mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
feat(sqlite): make worker deadlines adjustable with recovery guidance (#5818)
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)
This commit is contained in:
@@ -4,11 +4,13 @@ import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCo
|
||||
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",
|
||||
@@ -43,6 +45,10 @@ it.each([
|
||||
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) => {
|
||||
@@ -58,6 +64,70 @@ it.each([
|
||||
},
|
||||
);
|
||||
|
||||
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",
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type InspectSqliteDatabaseInput,
|
||||
type SqliteDatabase,
|
||||
SQLITE_ROW_LIMIT_DEFAULT,
|
||||
DEFAULT_SQLITE_WORKER_TIMEOUT_MS,
|
||||
sqliteSchemaScope,
|
||||
sqliteInspectionParameters,
|
||||
} from "../../domain/sqlite/sqliteDatabase.js";
|
||||
@@ -36,6 +37,9 @@ const matchesSelectedDatabase = (
|
||||
subject.path === input.path &&
|
||||
subject.sha256 === report.artifact.sha256 &&
|
||||
subject.format === "file" &&
|
||||
// Fresh executions must report known settings; only historical parsing permits omission.
|
||||
report.worker_timeout_ms ===
|
||||
(input.worker_timeout_ms ?? DEFAULT_SQLITE_WORKER_TIMEOUT_MS) &&
|
||||
(report.wal === null || report.wal.path === `${input.path}-wal`) &&
|
||||
report.schema.scope === sqliteSchemaScope(input) &&
|
||||
(report.schema.scope !== "selected_table" ||
|
||||
@@ -105,7 +109,7 @@ export class SqliteDatabaseService {
|
||||
return err(
|
||||
new AnalysisOutputError(
|
||||
OPERATION,
|
||||
"SQLite adapter returned malformed data or changed the selected database identity/table/row limit.",
|
||||
"SQLite adapter returned malformed data or changed the selected database identity/table/row limit/worker budget.",
|
||||
),
|
||||
);
|
||||
return ok(
|
||||
|
||||
Reference in New Issue
Block a user