349 lines · 14.2 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:off - test builds fixtures via Node fs/path directly.// @effect-diagnostics preferSchemaOverJson:off - fixtures serialize plain JSON journal records.import * as NodeFSP from "node:fs/promises";import * as NodePath from "node:path"; import { assert, describe, it } from "@effect/vitest";import * as NodeServices from "@effect/platform-node/NodeServices";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem"; import * as WorkflowInspectionService from "./WorkflowInspectionService.ts"; interface Layout {  readonly root: string;  readonly transcriptDir: string;  readonly scriptsDir: string;  readonly scriptPath: string;} const makeLayout = (fs: FileSystem.FileSystem, root: string) =>  Effect.gen(function* () {    const sessionDir = NodePath.join(root, "proj", "sess");    const transcriptDir = NodePath.join(sessionDir, "subagents", "workflows", "wf_abc");    const scriptsDir = NodePath.join(sessionDir, "workflows", "scripts");    yield* fs.makeDirectory(transcriptDir, { recursive: true });    yield* fs.makeDirectory(scriptsDir, { recursive: true });    return {      root,      transcriptDir,      scriptsDir,      scriptPath: NodePath.join(scriptsDir, "spec.js"),    } satisfies Layout;  }); /** Build a service instance whose projects root is an isolated temp dir. */const setup = Effect.gen(function* () {  const fs = yield* FileSystem.FileSystem;  const root = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-inspection-" });  const layout = yield* makeLayout(fs, root);  const service = yield* WorkflowInspectionService.make({ projectsRoot: root });  return { fs, service, layout };}); describe("WorkflowInspectionService", () => {  describe("readScript", () => {    it.effect("reads a contained script and reports it untruncated", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        yield* fs.writeFileString(layout.scriptPath, "export const run = () => 1;\n");         const result = yield* service.readScript({ scriptPath: layout.scriptPath });        assert.equal(result.source, "export const run = () => 1;\n");        assert.isFalse(result.truncated);      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("clips scripts larger than the cap and marks them truncated", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        const big = "a".repeat(512 * 1024 + 128);        yield* fs.writeFileString(layout.scriptPath, big);         const result = yield* service.readScript({ scriptPath: layout.scriptPath });        assert.isTrue(result.truncated);        assert.equal(result.source.length, 512 * 1024);      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("rejects a relative path as invalid-path", () =>      Effect.gen(function* () {        const { service } = yield* setup;        const error = yield* service          .readScript({ scriptPath: "relative/spec.js" })          .pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "invalid-path");      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("rejects a path outside the projects root as invalid-path", () =>      Effect.gen(function* () {        const { fs, service } = yield* setup;        const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" });        const outsideScript = NodePath.join(outside, "escape.js");        yield* fs.writeFileString(outsideScript, "export const x = 1;");         const error = yield* service.readScript({ scriptPath: outsideScript }).pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "invalid-path");      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("rejects a symlink inside the root that escapes it as invalid-path", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" });        const outsideScript = NodePath.join(outside, "real.js");        yield* fs.writeFileString(outsideScript, "export const x = 1;");         const linkPath = NodePath.join(layout.scriptsDir, "link.js");        yield* Effect.promise(() => NodeFSP.symlink(outsideScript, linkPath));         const error = yield* service.readScript({ scriptPath: linkPath }).pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "invalid-path");      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("rejects a non-script extension as invalid-path", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        const badPath = NodePath.join(layout.scriptsDir, "spec.txt");        yield* fs.writeFileString(badPath, "not a script");         const error = yield* service.readScript({ scriptPath: badPath }).pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "invalid-path");      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("reports a missing script as not-found", () =>      Effect.gen(function* () {        const { service, layout } = yield* setup;        const missing = NodePath.join(layout.scriptsDir, "missing.js");        const error = yield* service.readScript({ scriptPath: missing }).pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "not-found");      }).pipe(Effect.provide(NodeServices.layer)),    );  });   describe("readJournal", () => {    it.effect("summarizes started and result records with clipping", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        const bigResult = "z".repeat(64 * 1024);        const lines = [          JSON.stringify({ type: "started", key: "k1", agentId: "a1" }),          JSON.stringify({            type: "result",            key: "k1",            agentId: "a1",            result: { ok: true, value: 42 },          }),          JSON.stringify({ type: "started", key: "k2", agentId: "a2" }),          "this-is-not-json{",          JSON.stringify({ type: "result", key: "k3", agentId: "a3", result: bigResult }),        ];        yield* fs.writeFileString(          NodePath.join(layout.transcriptDir, "journal.jsonl"),          `${lines.join("\n")}\n`,        );         const result = yield* service.readJournal({ transcriptDir: layout.transcriptDir });        assert.isFalse(result.truncated);        assert.deepEqual(          result.entries.map((entry) => entry.agentId),          ["a1", "a2", "a3"],        );         const a1 = result.entries[0];        assert.isTrue(a1?.hasResult);        assert.equal(a1?.resultJson, JSON.stringify({ ok: true, value: 42 }));        assert.isUndefined(a1?.resultTruncated);         const a2 = result.entries[1];        assert.isFalse(a2?.hasResult);        assert.isUndefined(a2?.resultJson);         const a3 = result.entries[2];        assert.isTrue(a3?.hasResult);        assert.isTrue(a3?.resultTruncated);        assert.equal(a3?.resultJson?.length, 32 * 1024);      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("reports a missing journal as not-found", () =>      Effect.gen(function* () {        const { service, layout } = yield* setup;        const error = yield* service          .readJournal({ transcriptDir: layout.transcriptDir })          .pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "not-found");      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("rejects a journal.jsonl symlink that escapes the root as invalid-path", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" });        const secret = NodePath.join(outside, "secret.jsonl");        yield* fs.writeFileString(secret, JSON.stringify({ type: "started", agentId: "leak" }));         const escapeDir = NodePath.join(layout.transcriptDir, "..", "wf_journal_escape");        yield* fs.makeDirectory(escapeDir, { recursive: true });        yield* Effect.promise(() =>          NodeFSP.symlink(secret, NodePath.join(escapeDir, "journal.jsonl")),        );         const error = yield* service.readJournal({ transcriptDir: escapeDir }).pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "invalid-path");      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("rejects a transcript dir outside the root as invalid-path", () =>      Effect.gen(function* () {        const { fs, service } = yield* setup;        const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" });        yield* fs.writeFileString(NodePath.join(outside, "journal.jsonl"), "");         const error = yield* service.readJournal({ transcriptDir: outside }).pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "invalid-path");      }).pipe(Effect.provide(NodeServices.layer)),    );  });   describe("readAgentTranscript", () => {    it.effect("reads the full transcript and reports completion", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        yield* fs.writeFileString(          NodePath.join(layout.transcriptDir, "agent-a1.jsonl"),          "l0\nl1\nl2\n",        );         const result = yield* service.readAgentTranscript({          transcriptDir: layout.transcriptDir,          agentId: "a1",        });        assert.deepEqual(result.lines, ["l0", "l1", "l2"]);        assert.equal(result.nextLine, 3);        assert.isTrue(result.complete);      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("returns the remainder from a mid-file cursor", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        yield* fs.writeFileString(          NodePath.join(layout.transcriptDir, "agent-a1.jsonl"),          "l0\nl1\nl2\n",        );         const result = yield* service.readAgentTranscript({          transcriptDir: layout.transcriptDir,          agentId: "a1",          afterLine: 1,        });        assert.deepEqual(result.lines, ["l1", "l2"]);        assert.equal(result.nextLine, 3);        assert.isTrue(result.complete);      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("returns empty and complete when the cursor is past end-of-file", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        yield* fs.writeFileString(          NodePath.join(layout.transcriptDir, "agent-a1.jsonl"),          "l0\nl1\nl2\n",        );         const result = yield* service.readAgentTranscript({          transcriptDir: layout.transcriptDir,          agentId: "a1",          afterLine: 10,        });        assert.deepEqual(result.lines, []);        assert.equal(result.nextLine, 10);        assert.isTrue(result.complete);      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("rejects a traversal agent id as invalid-path", () =>      Effect.gen(function* () {        const { service, layout } = yield* setup;        const error = yield* service          .readAgentTranscript({ transcriptDir: layout.transcriptDir, agentId: "../journal" })          .pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "invalid-path");      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("pages a long transcript at the line cap", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        const lines = Array.from({ length: 500 }, (_unused, index) => `line-${index}`);        yield* fs.writeFileString(          NodePath.join(layout.transcriptDir, "agent-a1.jsonl"),          lines.join("\n"),        );         const first = yield* service.readAgentTranscript({          transcriptDir: layout.transcriptDir,          agentId: "a1",        });        assert.equal(first.lines.length, 400);        assert.equal(first.nextLine, 400);        assert.isFalse(first.complete);         const second = yield* service.readAgentTranscript({          transcriptDir: layout.transcriptDir,          agentId: "a1",          afterLine: first.nextLine,        });        assert.equal(second.lines.length, 100);        assert.equal(second.nextLine, 500);        assert.isTrue(second.complete);        assert.equal(second.lines[99], "line-499");      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("rejects an agent transcript symlink that escapes the root as invalid-path", () =>      Effect.gen(function* () {        const { fs, service, layout } = yield* setup;        const outside = yield* fs.makeTempDirectoryScoped({ prefix: "t3-workflow-outside-" });        const secret = NodePath.join(outside, "secret.txt");        yield* fs.writeFileString(secret, "root:x:0:0::/root:/bin/bash");         const escapeDir = NodePath.join(layout.transcriptDir, "..", "wf_transcript_escape");        yield* fs.makeDirectory(escapeDir, { recursive: true });        yield* Effect.promise(() =>          NodeFSP.symlink(secret, NodePath.join(escapeDir, "agent-leak.jsonl")),        );         const error = yield* service          .readAgentTranscript({ transcriptDir: escapeDir, agentId: "leak" })          .pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "invalid-path");      }).pipe(Effect.provide(NodeServices.layer)),    );     it.effect("reports a missing transcript as not-found", () =>      Effect.gen(function* () {        const { service, layout } = yield* setup;        const error = yield* service          .readAgentTranscript({ transcriptDir: layout.transcriptDir, agentId: "missing" })          .pipe(Effect.flip);        assert.equal(error._tag, "WorkflowInspectionError");        assert.equal(error.reason, "not-found");      }).pipe(Effect.provide(NodeServices.layer)),    );  });});