@effect-agent/testing 0.1.0-beta.6 → 0.1.0-beta.60

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.
Files changed (54) hide show
  1. package/dist/Certification.d.mts +105 -0
  2. package/dist/Certification.mjs +590 -0
  3. package/dist/Certification.mjs.map +1 -0
  4. package/dist/Chaos.d.mts +126 -0
  5. package/dist/Chaos.mjs +755 -0
  6. package/dist/Chaos.mjs.map +1 -0
  7. package/dist/CodeExecutorConformance.d.mts +33 -0
  8. package/dist/CodeExecutorConformance.mjs +231 -0
  9. package/dist/CodeExecutorConformance.mjs.map +1 -0
  10. package/dist/CodeExecutorSubstitute.d.mts +21 -0
  11. package/dist/CodeExecutorSubstitute.mjs +364 -0
  12. package/dist/CodeExecutorSubstitute.mjs.map +1 -0
  13. package/dist/DocsResearcher.d.mts +270 -0
  14. package/dist/DocsResearcher.mjs +490 -0
  15. package/dist/DocsResearcher.mjs.map +1 -0
  16. package/dist/ScriptedModel-DAvxIiud.d.mts +220 -0
  17. package/dist/ScriptedModel.d.mts +2 -0
  18. package/dist/ScriptedModel.mjs +155 -0
  19. package/dist/ScriptedModel.mjs.map +1 -0
  20. package/dist/TravelPlanner.d.mts +1665 -0
  21. package/dist/TravelPlanner.mjs +1958 -0
  22. package/dist/TravelPlanner.mjs.map +1 -0
  23. package/dist/deterministic-layers-Eka0fMZq.mjs +358 -0
  24. package/dist/deterministic-layers-Eka0fMZq.mjs.map +1 -0
  25. package/dist/index.d.mts +2 -3378
  26. package/dist/index.mjs +2 -4766
  27. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  28. package/package.json +1 -48
  29. package/src/{certification.ts → Certification.ts} +251 -115
  30. package/src/{chaos.ts → Chaos.ts} +246 -122
  31. package/src/{code-executor-conformance.ts → CodeExecutorConformance.ts} +29 -6
  32. package/src/{code-executor-substitute.ts → CodeExecutorSubstitute.ts} +104 -42
  33. package/src/{fixtures/docs-researcher/index.ts → DocsResearcher.ts} +68 -5
  34. package/src/{scripted-model.ts → ScriptedModel.ts} +22 -25
  35. package/src/TravelPlanner.ts +232 -0
  36. package/src/fixtures/docs-researcher/definition.ts +19 -10
  37. package/src/fixtures/docs-researcher/harness.ts +41 -30
  38. package/src/fixtures/docs-researcher/mcp.ts +49 -3
  39. package/src/fixtures/travel-planner/definition.ts +15 -2
  40. package/src/fixtures/travel-planner/deterministic-layers.ts +47 -4
  41. package/src/fixtures/travel-planner/phase2.ts +4 -3
  42. package/src/fixtures/travel-planner/phase3.ts +18 -37
  43. package/src/fixtures/travel-planner/phase4.ts +23 -36
  44. package/src/fixtures/travel-planner/phase5.ts +38 -41
  45. package/src/fixtures/travel-planner/phase6.ts +182 -84
  46. package/src/fixtures/travel-planner/phase7.ts +4 -102
  47. package/src/fixtures/travel-planner/scenarios.ts +3 -4
  48. package/src/fixtures/travel-planner/subagents-durable.ts +33 -57
  49. package/src/fixtures/travel-planner/subagents.ts +35 -13
  50. package/src/index.ts +1 -11
  51. package/dist/index.mjs.map +0 -1
  52. package/src/code-executor-conformance.d.ts +0 -30
  53. package/src/fixtures/travel-planner/index.ts +0 -11
  54. package/src/fixtures/warehouse/index.ts +0 -412
@@ -1,30 +0,0 @@
1
- import { CodeExecutor, SandboxImplementation } from "@effect-agent/sandbox";
2
- import { Effect, Schema } from "effect";
3
- declare const CodeExecutorConformanceViolation_base: Schema.Class<CodeExecutorConformanceViolation, Schema.TaggedStruct<"CodeExecutorConformanceViolation", {
4
- readonly caseName: Schema.String;
5
- readonly message: Schema.String;
6
- }>, import("effect/Cause").YieldableError>;
7
- /**
8
- * Shared `CodeExecutor` conformance (TEST-015). Every adapter — the
9
- * deterministic `unisolated` substitute and each isolated adapter — runs
10
- * `codeExecutorConformanceCases` verbatim. Enforcement cases that only genuine
11
- * isolation can prove (ambient network denial, synchronous CPU runaway
12
- * termination) are NOT here; they belong to isolated adapters only
13
- * (testing spec §8.1).
14
- *
15
- * Cases assume the live `Clock` (the wall-clock case uses a short real
16
- * deadline) and take one fresh executor pass per case, so a suite may share
17
- * one executor Layer across cases.
18
- */
19
- export declare class CodeExecutorConformanceViolation extends CodeExecutorConformanceViolation_base {
20
- }
21
- export interface CodeExecutorConformanceCase {
22
- readonly name: string;
23
- readonly run: Effect.Effect<void, CodeExecutorConformanceViolation, CodeExecutor>;
24
- }
25
- export interface CodeExecutorConformanceOptions {
26
- /** The posture the adapter under test must stamp on results and errors. */
27
- readonly implementation: SandboxImplementation;
28
- }
29
- export declare const codeExecutorConformanceCases: (options: CodeExecutorConformanceOptions) => ReadonlyArray<CodeExecutorConformanceCase>;
30
- export {};
@@ -1,11 +0,0 @@
1
- export * from "./definition.ts";
2
- export * from "./deterministic-layers.ts";
3
- export * from "./phase2.ts";
4
- export * from "./phase3.ts";
5
- export * from "./phase4.ts";
6
- export * from "./phase5.ts";
7
- export * from "./phase6.ts";
8
- export * from "./phase7.ts";
9
- export * from "./scenarios.ts";
10
- export * from "./subagents.ts";
11
- export * from "./subagents-durable.ts";
@@ -1,412 +0,0 @@
1
- import { ToolExecutionClass } from "@effect-agent/engine";
2
- import { SqliteClient } from "@effect/sql-sqlite-node";
3
- import { Context, Duration, Effect, Layer, Option, Predicate, Schema } from "effect";
4
- import { Tool, Toolkit } from "effect/unstable/ai";
5
- import * as SqlClient from "effect/unstable/sql/SqlClient";
6
- import { SqlError } from "effect/unstable/sql/SqlError";
7
-
8
- /**
9
- * The C3 read-only SQL reference integration (plan §8, SEC-015): application
10
- * code built from native Effect AI primitives proving that generated Code
11
- * Mode programs can query an internal service without the executor ever
12
- * seeing a connection, credential, or network authority.
13
- *
14
- * The guarantee is database authority and data topology, not SQL text
15
- * inspection: the fixture materializes a curated, tenant-scoped copy of the
16
- * warehouse into a private in-memory SQLite database (cross-tenant rows are
17
- * physically absent), then locks the connection with `PRAGMA query_only = ON`
18
- * so the engine itself denies every write. The keyword scanner underneath is
19
- * defense in depth for the escape hatches `query_only` cannot close (PRAGMA
20
- * could re-enable writes; ATTACH reaches the filesystem) — it is never the
21
- * primary boundary.
22
- *
23
- * Statement timeout: the synchronous SQLite driver cannot cancel a running
24
- * statement, so a `statementTimeout` request is rejected typed at Layer
25
- * construction rather than silently unenforced (CAP-015 posture). Wall-clock
26
- * bounding of a whole pass belongs to the Run duration budget and, in Code
27
- * Mode, the executor's own deadline.
28
- */
29
-
30
- const BoundedSqlText = Schema.NonEmptyString.check(Schema.isMaxLength(16 * 1024));
31
- const SqlParameter = Schema.Union([Schema.String, Schema.Number, Schema.Boolean, Schema.Null]);
32
-
33
- /** One curated multi-tenant seed row; only the configured tenant's rows are materialized. */
34
- export class WarehouseSeedRow extends Schema.Class<WarehouseSeedRow>(
35
- "@effect-agent/testing/WarehouseSeedRow",
36
- )({
37
- tenant: Schema.NonEmptyString,
38
- customer: Schema.NonEmptyString,
39
- region: Schema.NonEmptyString,
40
- revenue: Schema.Int,
41
- createdAt: Schema.NonEmptyString,
42
- }) {}
43
-
44
- export class WarehouseLimits extends Schema.Class<WarehouseLimits>(
45
- "@effect-agent/testing/WarehouseLimits",
46
- )({
47
- maxRows: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(10_000)),
48
- maxResultBytes: Schema.Int.check(
49
- Schema.isGreaterThan(0),
50
- Schema.isLessThanOrEqualTo(4 * 1024 * 1024),
51
- ),
52
- }) {}
53
-
54
- const defaultLimits = WarehouseLimits.make({ maxRows: 256, maxResultBytes: 256 * 1024 });
55
-
56
- /** The query was denied before or by the read-only database authority. */
57
- export class WarehouseQueryDenied extends Schema.TaggedError<WarehouseQueryDenied>()(
58
- "WarehouseQueryDenied",
59
- {
60
- reason: Schema.Literals(["write-denied", "multi-statement", "denied-keyword"]),
61
- message: Schema.String.check(Schema.isMaxLength(2 * 1024)),
62
- },
63
- ) {}
64
-
65
- /** The database rejected the query for an ordinary reason (syntax, missing view). */
66
- export class WarehouseQueryFailed extends Schema.TaggedError<WarehouseQueryFailed>()(
67
- "WarehouseQueryFailed",
68
- {
69
- message: Schema.String.check(Schema.isMaxLength(2 * 1024)),
70
- },
71
- ) {}
72
-
73
- /** The fixture cannot honestly enforce a requested policy (CAP-015). */
74
- export class WarehouseConfigurationError extends Schema.TaggedError<WarehouseConfigurationError>()(
75
- "WarehouseConfigurationError",
76
- {
77
- message: Schema.String,
78
- },
79
- ) {}
80
-
81
- export class WarehouseQuerySuccess extends Schema.Class<WarehouseQuerySuccess>(
82
- "@effect-agent/testing/WarehouseQuerySuccess",
83
- )({
84
- columns: Schema.Array(Schema.String).check(Schema.isMaxLength(64)),
85
- rows: Schema.Array(Schema.Record(Schema.String, Schema.Json)).check(Schema.isMaxLength(10_000)),
86
- rowCount: Schema.Natural,
87
- truncated: Schema.Boolean,
88
- }) {}
89
-
90
- /**
91
- * The read-only warehouse query service. Tenant identity is fixed at Layer
92
- * construction by the host — never by model-controlled arguments (SEC-015).
93
- */
94
- export class WarehouseDb extends Context.Service<
95
- WarehouseDb,
96
- {
97
- readonly query: (
98
- sql: string,
99
- parameters: ReadonlyArray<string | number | boolean | null>,
100
- ) => Effect.Effect<WarehouseQuerySuccess, WarehouseQueryDenied | WarehouseQueryFailed>;
101
- }
102
- >()("@effect-agent/testing/WarehouseDb") {}
103
-
104
- // ---------------------------------------------------------------------------
105
- // Defense-in-depth SQL scanning: strips string literals and comments, then
106
- // rejects multi-statements and the escape-hatch keywords the read-only
107
- // database authority cannot police itself.
108
- // ---------------------------------------------------------------------------
109
-
110
- const stripLiteralsAndComments = (sql: string): string => {
111
- let output = "";
112
- let index = 0;
113
- while (index < sql.length) {
114
- const char = sql[index];
115
- if (char === "'") {
116
- index += 1;
117
- while (index < sql.length) {
118
- if (sql[index] === "'" && sql[index + 1] === "'") {
119
- index += 2;
120
- continue;
121
- }
122
- if (sql[index] === "'") {
123
- index += 1;
124
- break;
125
- }
126
- index += 1;
127
- }
128
- output += " ";
129
- continue;
130
- }
131
- if (char === "-" && sql[index + 1] === "-") {
132
- while (index < sql.length && sql[index] !== "\n") {
133
- index += 1;
134
- }
135
- continue;
136
- }
137
- if (char === "/" && sql[index + 1] === "*") {
138
- index += 2;
139
- while (index < sql.length && !(sql[index] === "*" && sql[index + 1] === "/")) {
140
- index += 1;
141
- }
142
- index += 2;
143
- continue;
144
- }
145
- output += char;
146
- index += 1;
147
- }
148
- return output;
149
- };
150
-
151
- const deniedKeywords = /\b(pragma|attach|detach|vacuum|load_extension)\b/i;
152
-
153
- const scanSql = (sql: string): WarehouseQueryDenied | undefined => {
154
- const stripped = stripLiteralsAndComments(sql);
155
- const semicolon = stripped.indexOf(";");
156
- if (semicolon !== -1 && stripped.slice(semicolon + 1).trim().length > 0) {
157
- return WarehouseQueryDenied.make({
158
- reason: "multi-statement",
159
- message: "Exactly one SQL statement is allowed per call",
160
- });
161
- }
162
- const denied = deniedKeywords.exec(stripped);
163
- if (denied !== null) {
164
- return WarehouseQueryDenied.make({
165
- reason: "denied-keyword",
166
- message: `${denied[1].toUpperCase()} is not available through the read-only warehouse`,
167
- });
168
- }
169
- return undefined;
170
- };
171
-
172
- /** Non-cryptographic FNV-1a 64-bit digest for audit correlation of SQL text. */
173
- const queryDigest = (sql: string): string => {
174
- let hash = 0xcbf29ce484222325n;
175
- for (let index = 0; index < sql.length; index += 1) {
176
- hash ^= BigInt(sql.charCodeAt(index));
177
- hash = (hash * 0x100000001b3n) & 0xffffffffffffffffn;
178
- }
179
- return hash.toString(16).padStart(16, "0");
180
- };
181
-
182
- const utf8ByteLength = (value: string): number => {
183
- let total = 0;
184
- for (const character of value) {
185
- const codePoint = character.codePointAt(0) ?? 0;
186
- total += codePoint <= 0x7f ? 1 : codePoint <= 0x7ff ? 2 : codePoint <= 0xffff ? 3 : 4;
187
- }
188
- return total;
189
- };
190
-
191
- const decodeRow = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema.Json));
192
-
193
- /**
194
- * The read-only authority denial is structural: node-sqlite reports SQLite
195
- * result code 8 (`SQLITE_READONLY`; extended codes keep 8 in the low byte)
196
- * on the reason's cause. No error-text matching.
197
- */
198
- const isWriteDenial = (error: SqlError): boolean => {
199
- const cause = (error.reason as { readonly cause?: unknown }).cause;
200
- return (
201
- Predicate.isObject(cause) &&
202
- "errcode" in cause &&
203
- typeof cause.errcode === "number" &&
204
- (cause.errcode & 0xff) === 8
205
- );
206
- };
207
-
208
- const sqlFailureMessage = (error: SqlError): string => {
209
- const cause = (error.reason as { readonly cause?: unknown }).cause;
210
- const detail =
211
- Predicate.isObject(cause) && "errstr" in cause && typeof cause.errstr === "string"
212
- ? `: ${cause.errstr}`
213
- : "";
214
- return `${error.reason.message ?? "SQL execution failed"}${detail}`.slice(0, 2_000);
215
- };
216
-
217
- export interface WarehouseOptions {
218
- /** Host-owned tenant identity; only this tenant's rows are materialized. */
219
- readonly tenant: string;
220
- readonly seed: ReadonlyArray<WarehouseSeedRow>;
221
- readonly limits?: WarehouseLimits | undefined;
222
- /**
223
- * Not supported: the synchronous driver cannot cancel a statement, so any
224
- * requested timeout fails Layer construction typed instead of being
225
- * silently unenforced.
226
- */
227
- readonly statementTimeout?: Duration.Duration | undefined;
228
- }
229
-
230
- /**
231
- * Build the tenant-scoped read-only warehouse. The returned Layer owns the
232
- * private in-memory database; nothing else ever sees the connection.
233
- */
234
- export const warehouseDbLayer = (
235
- options: WarehouseOptions,
236
- ): Layer.Layer<WarehouseDb, WarehouseConfigurationError | SqlError> =>
237
- Layer.effect(WarehouseDb)(
238
- Effect.gen(function* () {
239
- if (options.statementTimeout !== undefined) {
240
- return yield* WarehouseConfigurationError.make({
241
- message:
242
- "The reference warehouse cannot enforce a statement timeout on the synchronous SQLite driver and refuses to pretend otherwise; bound the enclosing pass instead",
243
- });
244
- }
245
- const limits = options.limits ?? defaultLimits;
246
- const sql = yield* SqlClient.SqlClient;
247
-
248
- // Curated, tenant-scoped topology: only this tenant's rows exist in the
249
- // database the tool can reach, so cross-tenant reads are physically
250
- // impossible rather than merely filtered.
251
- yield* sql`CREATE TABLE invoice_summary (
252
- customer TEXT NOT NULL,
253
- region TEXT NOT NULL,
254
- revenue INTEGER NOT NULL,
255
- created_at TEXT NOT NULL
256
- )`;
257
- const tenantRows = options.seed.filter((row) => row.tenant === options.tenant);
258
- for (const row of tenantRows) {
259
- yield* sql`INSERT INTO invoice_summary ${sql.insert({
260
- customer: row.customer,
261
- region: row.region,
262
- revenue: row.revenue,
263
- created_at: row.createdAt,
264
- })}`;
265
- }
266
- // Database authority: the same connection now refuses every write.
267
- yield* sql`PRAGMA query_only = ON`.withoutTransform;
268
-
269
- const query = Effect.fn("WarehouseDb.query")(function* (
270
- text: string,
271
- parameters: ReadonlyArray<string | number | boolean | null>,
272
- ) {
273
- const denied = scanSql(text);
274
- if (denied !== undefined) {
275
- return yield* denied;
276
- }
277
- const rawRows = yield* sql
278
- .unsafe<Record<string, unknown>>(text, parameters as Array<unknown>)
279
- .withoutTransform.pipe(
280
- Effect.catchTag("SqlError", (error) =>
281
- Effect.fail<WarehouseQueryDenied | WarehouseQueryFailed>(
282
- isWriteDenial(error)
283
- ? WarehouseQueryDenied.make({
284
- reason: "write-denied",
285
- message: "The warehouse database authority is read-only",
286
- })
287
- : WarehouseQueryFailed.make({
288
- message: sqlFailureMessage(error),
289
- }),
290
- ),
291
- ),
292
- );
293
- const rows: Array<Record<string, Schema.Json>> = [];
294
- let truncated = false;
295
- let usedBytes = 0;
296
- for (const raw of rawRows) {
297
- if (rows.length >= limits.maxRows) {
298
- truncated = true;
299
- break;
300
- }
301
- const decoded = decodeRow(raw);
302
- if (Option.isNone(decoded)) {
303
- return yield* WarehouseQueryFailed.make({
304
- message: "A result row contained a value outside the JSON surface",
305
- });
306
- }
307
- const bytes = utf8ByteLength(JSON.stringify(decoded.value));
308
- if (usedBytes + bytes > limits.maxResultBytes) {
309
- truncated = true;
310
- break;
311
- }
312
- usedBytes += bytes;
313
- rows.push(decoded.value);
314
- }
315
- const columns = rows.length === 0 ? [] : Object.keys(rows[0]);
316
- const success = WarehouseQuerySuccess.make({
317
- columns,
318
- rows,
319
- rowCount: rows.length,
320
- truncated,
321
- });
322
- // Structural audit metadata: digest and shape only — never SQL text,
323
- // parameter values, or row contents (SEC-015, spec §11).
324
- yield* Effect.logDebug("warehouse query settled").pipe(
325
- Effect.annotateLogs({
326
- queryDigest: queryDigest(text),
327
- parameterCount: parameters.length,
328
- rowCount: success.rowCount,
329
- truncated: success.truncated,
330
- }),
331
- );
332
- return success;
333
- });
334
-
335
- return WarehouseDb.of({ query });
336
- }),
337
- ).pipe(Layer.provide(SqliteClient.layer({ filename: ":memory:" })));
338
-
339
- /**
340
- * The native read-only SQL Tool over the curated warehouse (plan §8.3). The
341
- * curated schema is part of the model-facing description; results report
342
- * truncation honestly (`truncated: true` success, D-035 default).
343
- */
344
- export const warehouseQueryTool = Tool.make("query_warehouse", {
345
- description:
346
- "Run one read-only SQL statement against the curated warehouse view `invoice_summary` (columns: customer TEXT, region TEXT, revenue INTEGER, created_at TEXT). Use ? placeholders with the parameters array; exactly one statement per call. Oversized results return truncated: true.",
347
- parameters: Schema.Struct({
348
- sql: BoundedSqlText,
349
- parameters: Schema.optionalKey(Schema.Array(SqlParameter).check(Schema.isMaxLength(32))),
350
- }),
351
- success: WarehouseQuerySuccess,
352
- failure: Schema.Union([WarehouseQueryDenied, WarehouseQueryFailed]),
353
- })
354
- .annotate(Tool.Readonly, true)
355
- .annotate(ToolExecutionClass, "readonly");
356
-
357
- export const warehouseToolkit = Toolkit.make(warehouseQueryTool);
358
-
359
- /** Handler Layer binding the Tool to the tenant-scoped read-only service. */
360
- export const warehouseHandlersLayer = warehouseToolkit.toLayer(
361
- Effect.gen(function* () {
362
- const db = yield* WarehouseDb;
363
- return {
364
- query_warehouse: ({
365
- sql,
366
- parameters,
367
- }: {
368
- readonly sql: string;
369
- readonly parameters?: ReadonlyArray<string | number | boolean | null> | undefined;
370
- }) => db.query(sql, parameters ?? []),
371
- };
372
- }),
373
- );
374
-
375
- /** A small deterministic multi-tenant seed shared by tests and the demo. */
376
- export const warehouseDemoSeed: ReadonlyArray<WarehouseSeedRow> = [
377
- WarehouseSeedRow.make({
378
- tenant: "acme",
379
- customer: "Stellar Freight",
380
- region: "emea",
381
- revenue: 48_200,
382
- createdAt: "2026-07-03",
383
- }),
384
- WarehouseSeedRow.make({
385
- tenant: "acme",
386
- customer: "Nimbus Analytics",
387
- region: "amer",
388
- revenue: 12_800,
389
- createdAt: "2026-07-11",
390
- }),
391
- WarehouseSeedRow.make({
392
- tenant: "acme",
393
- customer: "Copper Kettle Co",
394
- region: "amer",
395
- revenue: 730,
396
- createdAt: "2026-07-15",
397
- }),
398
- WarehouseSeedRow.make({
399
- tenant: "acme",
400
- customer: "Harbor Lights Ltd",
401
- region: "apac",
402
- revenue: 9_400,
403
- createdAt: "2026-07-21",
404
- }),
405
- WarehouseSeedRow.make({
406
- tenant: "umbra",
407
- customer: "Shadow Cartel",
408
- region: "emea",
409
- revenue: 999_999,
410
- createdAt: "2026-07-01",
411
- }),
412
- ];