mirror of
https://github.com/morluto/rea.git
synced 2026-10-30 03:15:54 +00:00
Expose a per-call SQLite worker deadline with exact CLI/MCP recovery controls, parent-owned execution metadata and historical unknown preservation. Keep fixed resource limits and cleanup admission intact. Share typed cleanup composition with one captured-output owner and preserve the timeout after cleanup resolves. Fixes #5812. Follow-up tracking: #5808. Existing CLI test-oracle failure: #5845. Verified 155 SQLite tests (3 skipped), static/docs/discovery checks, six real CLI/MCP pairs and 13 actual Codex sessions including four corrected deadline recoveries. Three pre-dispatch discovery failures remain explicit, not passes. Review findings include real retained-cleanup and fixture ownership regressions. Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
240 lines
7.4 KiB
TypeScript
240 lines
7.4 KiB
TypeScript
import { expect, it } from "vitest";
|
|
import { SqliteDatabaseService } from "./SqliteDatabaseService.js";
|
|
import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js";
|
|
import { err } from "../../domain/result.js";
|
|
import { ok } from "../../domain/result.js";
|
|
import { createAnalysisExecution } from "../AnalysisProvider.js";
|
|
import { sqliteDatabaseSchema } from "../../domain/sqlite/sqliteDatabase.js";
|
|
|
|
const fixture = () => ({
|
|
artifact: { path: "/selected.db", sha256: "a".repeat(64), bytes: 4096 },
|
|
wal: null,
|
|
engine: { name: "SQLite", version: "3.50.2" },
|
|
worker_timeout_ms: 30_000,
|
|
database_encoding: "UTF-8",
|
|
schema: {
|
|
scope: "inventory",
|
|
completeness: "complete",
|
|
tables: [],
|
|
indexes: [],
|
|
views: [],
|
|
triggers: [],
|
|
},
|
|
rows: null,
|
|
limitations: [],
|
|
});
|
|
|
|
const execution = (value: unknown) =>
|
|
createAnalysisExecution(
|
|
value,
|
|
{ id: "sqlite", name: "SQLite", version: "3.50.2" },
|
|
{
|
|
subject: { path: "/selected.db", sha256: "a".repeat(64), format: "file" },
|
|
locations: [{ kind: "artifact-path", path: "/selected.db" }],
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
{ path: "relative.db" },
|
|
{ path: "/selected.db", approval: true },
|
|
{ path: "/selected.db", row_limit: 1 },
|
|
{ path: "/selected.db", table: 1 },
|
|
{ path: "/selected.db", table: "data", row_limit: 0 },
|
|
{
|
|
path: "/selected.db",
|
|
table: "data",
|
|
row_limit: Number.MAX_SAFE_INTEGER + 1,
|
|
},
|
|
...[0, -1, 1.5, 2_147_483_648, "1000", null].map((worker_timeout_ms) => ({
|
|
path: "/selected.db",
|
|
worker_timeout_ms,
|
|
})),
|
|
])(
|
|
"rejects invalid SQLite selection before provider effects: %j",
|
|
async (input) => {
|
|
const service = new SqliteDatabaseService({
|
|
inspect: () => {
|
|
throw new Error("Invalid selection must not read a database");
|
|
},
|
|
});
|
|
expect(await service.inspect(input)).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisInputError" },
|
|
});
|
|
},
|
|
);
|
|
|
|
it("preserves the selected worker budget in provider input and Evidence", async () => {
|
|
const report = { ...fixture(), worker_timeout_ms: 1234 };
|
|
const service = new SqliteDatabaseService({
|
|
inspect: async (input) => {
|
|
expect(input.worker_timeout_ms).toBe(1234);
|
|
return ok(execution(report));
|
|
},
|
|
});
|
|
const result = await service.inspect({
|
|
path: "/selected.db",
|
|
worker_timeout_ms: 1234,
|
|
});
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.parameters).toMatchObject({ worker_timeout_ms: 1234 });
|
|
expect(result.value.normalized_result).toMatchObject({
|
|
worker_timeout_ms: 1234,
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
{ selected: undefined, reported: undefined },
|
|
{ selected: undefined, reported: 1234 },
|
|
{ selected: 1234, reported: undefined },
|
|
{ selected: 1234, reported: 30_000 },
|
|
])(
|
|
"rejects a missing or changed fresh producer budget: %j",
|
|
async ({ selected, reported }) => {
|
|
const { worker_timeout_ms: _default, ...report } = fixture();
|
|
const service = new SqliteDatabaseService({
|
|
inspect: async () =>
|
|
ok(
|
|
execution({
|
|
...report,
|
|
...(reported === undefined ? {} : { worker_timeout_ms: reported }),
|
|
}),
|
|
),
|
|
});
|
|
expect(
|
|
await service.inspect({
|
|
path: "/selected.db",
|
|
...(selected === undefined ? {} : { worker_timeout_ms: selected }),
|
|
}),
|
|
).toMatchObject({ ok: false, error: { _tag: "AnalysisOutputError" } });
|
|
},
|
|
);
|
|
|
|
it("retains the actual default without inventing an explicit caller selection", async () => {
|
|
const service = new SqliteDatabaseService({
|
|
inspect: async () => ok(execution(fixture())),
|
|
});
|
|
const result = await service.inspect({ path: "/selected.db" });
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.normalized_result).toMatchObject({
|
|
worker_timeout_ms: 30_000,
|
|
});
|
|
expect(result.value.parameters).not.toHaveProperty("worker_timeout_ms");
|
|
});
|
|
|
|
it("parses historical report omissions as unknown without supplying a default", () => {
|
|
const { worker_timeout_ms: _default, ...historical } = fixture();
|
|
const parsed = sqliteDatabaseSchema.parse(historical);
|
|
expect(parsed).not.toHaveProperty("worker_timeout_ms");
|
|
});
|
|
|
|
it("preserves producer failures and rejects pre-cancelled requests before effects", async () => {
|
|
const failure = new AnalysisCapabilityUnavailableError(
|
|
"rea-sqlite",
|
|
"inspect_sqlite_database",
|
|
"Selected worker could not start",
|
|
);
|
|
const service = new SqliteDatabaseService({
|
|
inspect: () => Promise.resolve(err(failure)),
|
|
});
|
|
expect(await service.inspect({ path: "/selected.db" })).toEqual(err(failure));
|
|
const signal = AbortSignal.abort();
|
|
const untouched = new SqliteDatabaseService({
|
|
inspect: () => {
|
|
throw new Error("Cancelled selection must not read a database");
|
|
},
|
|
});
|
|
expect(
|
|
await untouched.inspect({ path: "/selected.db" }, { signal }),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisCancelledError" },
|
|
});
|
|
});
|
|
|
|
it.each(["path", "digest", "subject", "wal", "table"])(
|
|
"rejects producer results with changed caller binding: %s",
|
|
async (problem) => {
|
|
const value = fixture();
|
|
if (problem === "path") value.artifact.path = "/other.db";
|
|
if (problem === "digest") value.artifact.sha256 = "b".repeat(64);
|
|
const observed = execution({
|
|
...value,
|
|
...(problem === "wal"
|
|
? { wal: { path: "/other.db-wal", sha256: "b".repeat(64), bytes: 32 } }
|
|
: {}),
|
|
});
|
|
const service = new SqliteDatabaseService({
|
|
inspect: () =>
|
|
Promise.resolve(
|
|
ok({
|
|
...observed,
|
|
...(problem === "subject" ? { subject: null } : {}),
|
|
}),
|
|
),
|
|
});
|
|
expect(
|
|
await service.inspect({
|
|
path: "/selected.db",
|
|
...(problem === "table" ? { table: "selected" } : {}),
|
|
}),
|
|
).toMatchObject({ ok: false, error: { _tag: "AnalysisOutputError" } });
|
|
},
|
|
);
|
|
|
|
it("preserves completed observed evidence when cancellation arrives after provider completion", async () => {
|
|
const value = fixture();
|
|
const service = new SqliteDatabaseService({
|
|
inspect: () => Promise.resolve(ok(execution(value))),
|
|
});
|
|
expect(await service.inspect({ path: "/selected.db" })).toMatchObject({
|
|
ok: true,
|
|
value: {
|
|
confidence: "observed",
|
|
subject: {
|
|
local_path: "/selected.db",
|
|
digest: { sha256: "a".repeat(64) },
|
|
},
|
|
normalized_result: value,
|
|
},
|
|
});
|
|
const controller = new AbortController();
|
|
const cancelled = new SqliteDatabaseService({
|
|
inspect: () => {
|
|
controller.abort();
|
|
return Promise.resolve(ok(execution(value)));
|
|
},
|
|
});
|
|
expect(
|
|
await cancelled.inspect(
|
|
{ path: "/selected.db" },
|
|
{ signal: controller.signal },
|
|
),
|
|
).toMatchObject({
|
|
ok: true,
|
|
value: { normalized_result: value },
|
|
});
|
|
});
|
|
|
|
it("preserves the exact caller reason when inspection is already aborted", async () => {
|
|
const reason = Object.freeze({ requested: "stop" });
|
|
const controller = new AbortController();
|
|
controller.abort(reason);
|
|
let calls = 0;
|
|
const service = new SqliteDatabaseService({
|
|
inspect: () => {
|
|
calls += 1;
|
|
return Promise.reject(new Error("provider must not run"));
|
|
},
|
|
});
|
|
const result = await service.inspect(
|
|
{ path: "/selected.db" },
|
|
{ signal: controller.signal },
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) return;
|
|
expect(result.error._tag).toBe("AnalysisCancelledError");
|
|
expect(result.error.cause).toBe(reason);
|
|
expect(calls).toBe(0);
|
|
});
|