Files
rea/tests/acceptance/sqliteDatabaseCli.test.ts
T
Kaoru0822 5a22ae6bc8 fix(mcp)!: return a compact binary_session status by default (#7127)
* fix(mcp)!: return a compact binary_session status by default

binary_session {} with no target open returned about 66 KB, most of it
provider rejection diagnostics and candidate capability sets that the
skill's recovery flow re-reads on every retry. 6.8.0 grew it by 21%
because the shared regex capability limitation began repeating setting
bounds and step-by-step recovery for every search operation of every
provider (#6099, #6267, #6301, #7049) and IDA gained that limitation for
its two search operations (#4862).

The regex limitation now states the effective deadline and heap, their
sources and where to change them; the deadline and heap errors keep the
full recovery. binary_session gains detail: summary (default) or
complete. The summary keeps open state, target identity, the binding,
route capabilities, run, activity and health status, each candidate's
availability and target-support status, code and reason, and every
tool's availability, reason, remediation and modes, and lists the
fields it leaves out in omitted_fields. Complete detail returns the
previous shape. The CLI capabilities and providers commands keep
complete provider facts through the same application projection.

BREAKING CHANGE: binary_session {} no longer returns
analysis_provider_candidates[].availability.diagnostics,
target_support.diagnostics or capabilities, or tool_availability group
surface, effects and annotations. Pass {"detail": "complete"} to get
them.

Fixes #7109

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(mcp): key binary_session output fields by detail

The output schema accepted detail: "complete" with summary candidates or
tool groups, and a summary without omitted_fields, so detail did not
tell callers which fields existed. Each session kind is now crossed
with a detail-specific shape: a summary requires omitted_fields and
summary-only candidates and tool groups; complete requires diagnostics,
candidate capabilities and static tool flags and forbids omitted_fields.
Both the canonical schema and the SDK-validated advertised schema reject
mismatched shapes.

The verifier helper accepts summary candidates only for a validated MCP
summary; CLI provider output still requires candidate capabilities.
Regex limitation tests check values, sources and setting names instead
of whole sentences.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 05:29:03 -07:00

1347 lines
43 KiB
TypeScript

import { createHash } from "node:crypto";
import {
lstat,
readFile,
rename,
stat,
symlink,
truncate,
writeFile,
} from "node:fs/promises";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { expect, onTestFinished } from "vitest";
import { z } from "zod";
import {
expandProviderCapabilities,
expandToolAvailability,
providerCapabilitySet,
toolAvailabilitySummaryInventory,
} from "../../src/contracts/toolOutputSchemaPrimitives.js";
import {
createSqliteApplicationCollationFixture,
createSqliteLongNameFixture,
SQLITE_NATIVE_LIMITS_AVAILABLE,
} from "../fixtures/sqlite/database.js";
import { parseEvidence } from "../../src/domain/evidence.js";
import { sqliteDatabaseSchema } from "../../src/domain/sqlite/sqliteDatabase.js";
import { connectLocalToolsMcp } from "../fixtures/localToolsMcp.js";
import { parseMcpToolError } from "../fixtures/mcpToolError.js";
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
import { cliTest, runFileCli } from "../support/cli/cliFixture.js";
const digest = (bytes: Buffer): string =>
createHash("sha256").update(bytes).digest("hex");
for (const expectedErrno of ["ELOOP", "ENAMETOOLONG"] as const) {
cliTest.skipIf(expectedErrno === "ELOOP" && process.platform === "win32")(
`reports an initial ${expectedErrno} path failure through CLI and MCP`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-path-failure-");
const path =
expectedErrno === "ELOOP"
? join(root, "cycle-a", "selected.db")
: join(root, "a".repeat(300), "selected.db");
if (expectedErrno === "ELOOP") {
await symlink("cycle-b", join(root, "cycle-a"));
await symlink("cycle-a", join(root, "cycle-b"));
}
const result = await cli.run({
arguments: ["inspect-sqlite-database", path, "--json"],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(result.exitCode).toBe(1);
const expectedError = {
code: "invalid_request",
details: {
issues: [
{
path: ["path"],
reason: "invalid_value",
message: expect.stringContaining(expectedErrno),
},
],
},
};
expect(result.json).toMatchObject(expectedError);
const { call } = await connectLocalToolsMcp();
const response = await call("inspect_sqlite_database", { path });
expect(parseMcpToolError(response).error).toMatchObject(expectedError);
},
);
}
cliTest(
"reports the combined database and WAL byte budget through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-size-budget-");
const path = join(root, "selected.db");
const walPath = `${path}-wal`;
const maximum = 256 * 1024 * 1024;
const databaseBytes = maximum / 2;
const walBytes = databaseBytes + 1;
await writeFile(path, "");
await writeFile(walPath, "");
await truncate(path, databaseBytes);
await truncate(walPath, walBytes);
const before = await Promise.all(
[path, walPath].map(async (filePath) => ({
path: filePath,
state: await lstat(filePath, { bigint: true }),
})),
);
const cliResult = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--worker-timeout-ms",
"60000",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
const { call } = await connectLocalToolsMcp();
const mcpResponse = await call("inspect_sqlite_database", {
path,
worker_timeout_ms: 60000,
});
const mcpError = parseMcpToolError(mcpResponse).error;
const expectedError = {
code: "resource_constraint",
category: "resource_constraint",
retryable: false,
details: {
reported_limits: {
sqlite_snapshot_input_bytes: maximum,
observed_sqlite_snapshot_input_bytes: String(maximum + 1),
},
},
remediation: {
action: expect.stringContaining("supported export workflow"),
},
};
expect(cliResult.exitCode).toBe(1);
expect(cliResult.json).toMatchObject(expectedError);
expect(mcpError).toMatchObject(expectedError);
const after = await Promise.all(
before.map(async ({ path: filePath }) => ({
path: filePath,
state: await lstat(filePath, { bigint: true }),
})),
);
for (const [index, original] of before.entries()) {
const current = after[index];
expect(current?.state).toMatchObject({
dev: original.state.dev,
ino: original.state.ino,
mode: original.state.mode,
size: original.state.size,
mtimeNs: original.state.mtimeNs,
ctimeNs: original.state.ctimeNs,
});
}
},
);
cliTest.skipIf(process.platform === "win32" || !SQLITE_NATIVE_LIMITS_AVAILABLE)(
"reports an unavailable capability when a closed database sidecar path is too long",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-long-basename-");
const path = join(root, "x".repeat(255));
const originalPath = join(root, "selected.db");
const database = new DatabaseSync(originalPath);
try {
database.exec(
"CREATE TABLE selected(value TEXT); INSERT INTO selected VALUES ('retained');",
);
} finally {
database.close();
}
await rename(originalPath, path);
const selectedBefore = await lstat(path, { bigint: true });
await expect(lstat(`${path}-wal`)).rejects.toMatchObject({
code: "ENAMETOOLONG",
});
await expect(lstat(`${path}-journal`)).rejects.toMatchObject({
code: "ENAMETOOLONG",
});
const cliResult = await cli.run({
arguments: ["inspect-sqlite-database", path, "--json"],
environment: { REA_LOG_LEVEL: "silent" },
});
const { call } = await connectLocalToolsMcp();
const mcpResult = await call("inspect_sqlite_database", { path });
const expectedError = {
code: "capability_unavailable",
category: "unsupported_provider",
retryable: false,
message: expect.stringContaining("supported consistent offline export"),
details: {
provider_id: "sqlite",
operation: "inspect_sqlite_database",
reason: expect.stringContaining("ENAMETOOLONG"),
},
remediation: {
action: expect.stringContaining("supported consistent offline export"),
},
};
expect(cliResult.exitCode).toBe(1);
expect(cliResult.json).toMatchObject(expectedError);
expect(parseMcpToolError(mcpResult).error).toMatchObject(expectedError);
const selectedAfter = await lstat(path, { bigint: true });
expect(selectedAfter).toMatchObject({
dev: selectedBefore.dev,
ino: selectedBefore.ino,
mode: selectedBefore.mode,
size: selectedBefore.size,
mtimeNs: selectedBefore.mtimeNs,
ctimeNs: selectedBefore.ctimeNs,
});
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"bounds default context and projects exact columns without evaluating unrelated cells",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-selective-");
const path = join(root, "selected.db");
const database = new DatabaseSync(path);
try {
database.exec(`
CREATE TABLE selected(id INTEGER, "" TEXT, payload BLOB GENERATED ALWAYS AS (zeroblob(72*1024*1024)) VIRTUAL);
CREATE TABLE unrelated(value TEXT);
CREATE VIEW unrelated_view AS SELECT value FROM unrelated;
CREATE INDEX selected_id ON selected(id);
CREATE TRIGGER selected_trigger AFTER INSERT ON selected BEGIN SELECT 1; END;
WITH RECURSIVE numbers(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM numbers WHERE n<21)
INSERT INTO selected(id, "") SELECT n, 'retained' FROM numbers;
`);
} finally {
database.close();
}
const before = await readFile(path);
const { call } = await connectLocalToolsMcp();
const inventory = await call("inspect_sqlite_database", { path });
expect(inventory.isError).not.toBe(true);
const schema = z
.object({
normalized_result: z.object({
schema: z.object({
scope: z.literal("inventory"),
tables: z.array(
z.object({
name: z.string(),
columns: z.array(z.unknown()),
columns_completeness: z.literal("not_requested"),
}),
),
}),
}),
})
.parse(inventory.structuredContent).normalized_result.schema;
expect(schema.tables.map((table) => table.name)).toEqual([
"selected",
"unrelated",
]);
expect(schema.tables.every((table) => table.columns.length === 0)).toBe(
true,
);
expect(JSON.stringify(inventory.structuredContent)).not.toContain(
"zeroblob",
);
const selected = await call("inspect_sqlite_database", {
path,
table: "selected",
columns: ["", "id", "id"],
worker_timeout_ms: 42000,
});
expect(selected.isError).not.toBe(true);
expect(selected.structuredContent).toMatchObject({
parameters: { worker_timeout_ms: 42000 },
normalized_result: {
worker_timeout_ms: 42000,
schema: {
scope: "selected_table",
tables: [{ name: "selected" }],
views: [],
indexes: [{ name: "selected_id" }],
triggers: [{ name: "selected_trigger" }],
},
rows: {
columns: ["", "id", "id"],
row_limit: 20,
returned_rows: 20,
truncated: true,
values: expect.arrayContaining([
[
{
type: "text",
value: "retained",
bytes_hex: "72657461696e6564",
encoding: "UTF-8",
},
{ type: "integer", value: "1" },
{ type: "integer", value: "1" },
],
]),
},
},
});
const cliResult = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--table",
"selected",
"--column",
"id",
"--column",
"",
"--row-limit",
"1",
"--worker-timeout-ms",
"42000",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(cliResult.exitCode, cliResult.stdout + cliResult.stderr).toBe(0);
expect(cliResult.json).toMatchObject({
parameters: { worker_timeout_ms: 42000 },
normalized_result: {
worker_timeout_ms: 42000,
rows: { columns: ["id", ""], returned_rows: 1 },
},
});
for (const input of [
{ path, columns: ["id"] },
{ path, schema_scope: "selected_table" },
{ path, table: "selected", columns: [] },
{ path, table: "selected", columns: ["ID"] },
])
expect((await call("inspect_sqlite_database", input)).isError).toBe(true);
expect(Buffer.compare(await readFile(path), before)).toBe(0);
},
);
cliTest(
"discovers SQLite runtime availability before inspecting a database through CLI and MCP",
async ({ cli }) => {
const result = await cli.run({
arguments: ["capabilities", "--json"],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(result.exitCode, result.stdout + result.stderr).toBe(0);
const status = z
.object({
open: z.literal(false),
capabilities: z.array(providerCapabilitySet),
})
.parse(result.json);
expect(expandProviderCapabilities(status.capabilities)).toEqual(
expect.arrayContaining([
expect.objectContaining({
operation: "inspect_sqlite_database",
available: SQLITE_NATIVE_LIMITS_AVAILABLE,
reason: SQLITE_NATIVE_LIMITS_AVAILABLE
? null
: expect.stringContaining("Node.js 24.x >=24.15 or 26+"),
availability_code: SQLITE_NATIVE_LIMITS_AVAILABLE
? null
: "unsupported_version",
limitations: [expect.stringContaining("DatabaseSync.limits")],
effects: {
mutates_artifact: false,
launches_process: true,
may_show_ui: false,
may_access_network: false,
may_write_filesystem: true,
changes_permissions: false,
requires_root: false,
},
}),
]),
);
const { call } = await connectLocalToolsMcp();
const response = await call("binary_session", {});
expect(response.isError).not.toBe(true);
const sessionStatus = z
.object({
result: z.object({
tool_availability: toolAvailabilitySummaryInventory,
}),
})
.parse(response.structuredContent);
expect(
expandToolAvailability(sessionStatus.result.tool_availability),
).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "inspect_sqlite_database",
available: SQLITE_NATIVE_LIMITS_AVAILABLE,
remediation: SQLITE_NATIVE_LIMITS_AVAILABLE
? null
: expect.stringContaining("Node.js 24.x >=24.15 or 26+"),
}),
]),
);
},
);
cliTest.skipIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"reports the required native SQLite limits capability through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-unavailable-");
const path = join(root, "selected.db");
const database = new DatabaseSync(path);
try {
database.exec("CREATE TABLE items(value INTEGER)");
} finally {
database.close();
}
const result = await cli.run({
arguments: ["inspect-sqlite-database", path, "--json"],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(result.exitCode).toBe(1);
expect(result.json).toMatchObject({
code: "capability_unavailable",
message: expect.stringContaining("DatabaseSync.limits"),
});
const { call } = await connectLocalToolsMcp();
const response = await call("inspect_sqlite_database", { path });
expect(response.isError).toBe(true);
expect(parseMcpToolError(response).error).toMatchObject({
code: "capability_unavailable",
message: expect.stringContaining("DatabaseSync.limits"),
});
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"accepts small samples above 1000 rows within the real resource budgets",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-row-budget-");
const path = join(root, "rows.db");
const database = new DatabaseSync(path);
try {
database.exec(`CREATE TABLE items(value INTEGER);
WITH RECURSIVE numbers(value) AS (SELECT 1 UNION ALL SELECT value + 1 FROM numbers WHERE value < 1002)
INSERT INTO items SELECT value FROM numbers;`);
} finally {
database.close();
}
const { call } = await connectLocalToolsMcp();
for (const limit of [1001, Number.MAX_SAFE_INTEGER]) {
const result = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--table",
"items",
"--row-limit",
String(limit),
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(result.exitCode, result.stdout + result.stderr).toBe(0);
expect(result.json).toMatchObject({
normalized_result: {
rows: {
row_limit: limit,
returned_rows: Math.min(limit, 1002),
truncated: limit < 1002,
},
},
});
const response = await call("inspect_sqlite_database", {
path,
table: "items",
row_limit: limit,
});
expect(response.isError, JSON.stringify(response)).not.toBe(true);
expect(response.structuredContent).toEqual(result.json);
}
},
);
const sourceState = async (path: string) => {
const bytes = await readFile(path);
const metadata = await stat(path, { bigint: true });
return {
bytes,
device: metadata.dev,
inode: metadata.ino,
size: metadata.size,
modified: metadata.mtimeNs,
changed: metadata.ctimeNs,
};
};
const createLosslessSnapshot = (path: string, journalMode: string): void => {
const database = new DatabaseSync(path);
try {
database.exec(`PRAGMA journal_mode=${journalMode}`);
database.exec(`
CREATE TABLE "odd "" table" (
id INTEGER PRIMARY KEY,
payload BLOB,
amount REAL,
missing TEXT,
text_value TEXT,
negative_zero,
generated TEXT GENERATED ALWAYS AS (hex(payload)) VIRTUAL
);
CREATE UNIQUE INDEX "quoted "" index" ON "odd "" table"(payload, id);
CREATE VIEW readable AS SELECT id FROM "odd "" table";
CREATE TRIGGER observed AFTER INSERT ON "odd "" table" BEGIN SELECT 1; END;
CREATE TABLE keyed(k TEXT PRIMARY KEY) WITHOUT ROWID, STRICT;
`);
const insert = database.prepare(
'INSERT INTO "odd "" table"(id,payload,amount,missing,text_value,negative_zero) VALUES(?,?,?,NULL,CAST(X\'80ff\' AS TEXT),?)',
);
insert.setReadBigInts(true);
expect(
insert.run(
9007199254740993n,
Buffer.from("deadbeef", "hex"),
Infinity,
-0,
).lastInsertRowid,
).toBe(9007199254740993n);
insert.run(9007199254740994n, Buffer.from("deadbeef", "hex"), Infinity, -0);
} finally {
database.close();
}
};
cliTest
.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)
.for(["DELETE", "PERSIST", "TRUNCATE"])(
"inspects a real %s SQLite schema and lossless selected records identically through CLI and MCP",
async (journalMode, { cli }) => {
const root = await createTestTempDirectory("rea-sqlite-public-");
const path = join(root, "selected.db");
createLosslessSnapshot(path, journalMode);
const original = await readFile(path);
const result = await cli.run({
arguments: [
"inspect-sqlite-database",
"selected.db",
"--schema-scope",
"database",
"--table",
'odd " table',
"--row-limit",
"1",
"--json",
],
cwd: root,
environment: {
REA_LOG_LEVEL: "silent",
REA_ANALYSIS_PROVIDER: "ghidra",
REA_GHIDRA_HOME: "/unconfigured/ghidra",
},
});
expect(result.exitCode, result.stdout + result.stderr).toBe(0);
expect(result.json).toMatchObject({
operation: "inspect_sqlite_database",
confidence: "observed",
subject: { local_path: path, digest: { sha256: digest(original) } },
normalized_result: {
artifact: { path, sha256: digest(original), bytes: original.length },
wal: null,
schema: {
completeness: "complete",
tables: expect.arrayContaining([
expect.objectContaining({
name: 'odd " table',
columns: expect.arrayContaining([
expect.objectContaining({
name: "id",
primary_key_position: 1,
}),
expect.objectContaining({ name: "generated", hidden: 2 }),
]),
}),
expect.objectContaining({
name: "keyed",
without_rowid: true,
strict: true,
}),
]),
indexes: expect.arrayContaining([
expect.objectContaining({
name: 'quoted " index',
table: 'odd " table',
}),
]),
views: [
{ name: "readable", sql: expect.stringContaining("SELECT id") },
],
triggers: [
{
name: "observed",
table: 'odd " table',
sql: expect.stringContaining("AFTER INSERT"),
},
],
},
rows: {
table: 'odd " table',
columns: [
"id",
"payload",
"amount",
"missing",
"text_value",
"negative_zero",
"generated",
],
returned_rows: 1,
row_limit: 1,
truncated: true,
values: [
[
{
type: "integer",
value: expect.stringMatching(/^900719925474099[34]$/),
},
{ type: "blob", hex: "deadbeef" },
{ type: "real", value: "Infinity" },
{ type: "null" },
{ type: "text", value: "\ufffd\ufffd", bytes_hex: "80ff" },
{ type: "real", value: "-0" },
{
type: "text",
value: "DEADBEEF",
bytes_hex: "4445414442454546",
},
],
],
},
},
});
const { call } = await connectLocalToolsMcp();
const response = await call("inspect_sqlite_database", {
path,
table: 'odd " table',
schema_scope: "database",
row_limit: 1,
});
expect(response.isError, JSON.stringify(response)).not.toBe(true);
expect(response.structuredContent).toEqual(result.json);
expect(Buffer.compare(await readFile(path), original)).toBe(0);
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"includes uncheckpointed committed WAL data without modifying the source database or sidecars",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-wal-public-");
const path = join(root, "wal.db");
const database = new DatabaseSync(path);
onTestFinished(() => database.close());
database.exec(
"PRAGMA journal_mode=WAL; PRAGMA wal_autocheckpoint=0; CREATE TABLE committed(value TEXT); INSERT INTO committed VALUES('only in WAL');",
);
const files = [path, `${path}-wal`, `${path}-shm`];
const before = await Promise.all(files.map(sourceState));
const db = before[0];
const wal = before[1];
expect(db).toBeDefined();
expect(wal).toBeDefined();
const result = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--table",
"committed",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(result.exitCode, result.stdout + result.stderr).toBe(0);
expect(result.json).toMatchObject({
normalized_result: {
artifact: { sha256: db === undefined ? "missing" : digest(db.bytes) },
wal: {
path: `${path}-wal`,
sha256: wal === undefined ? "missing" : digest(wal.bytes),
},
rows: {
values: [
[
{
type: "text",
value: "only in WAL",
bytes_hex: Buffer.from("only in WAL").toString("hex"),
},
],
],
returned_rows: 1,
truncated: false,
},
},
});
const { call } = await connectLocalToolsMcp();
const response = await call("inspect_sqlite_database", {
path,
table: "committed",
});
expect(response.isError, JSON.stringify(response)).not.toBe(true);
expect(response.structuredContent).toEqual(result.json);
const after = await Promise.all(files.map(sourceState));
expect(after).toEqual(before);
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"returns actionable public errors for corrupt input, unknown or ambiguous tables and invalid record choices",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-errors-public-");
const path = join(root, "invalid.db");
const { call } = await connectLocalToolsMcp();
// Distinct malformed selections each name their own selected path.
for (const selected of [path, join(root, "other-invalid.db")]) {
await writeFile(selected, "not a SQLite database");
const expectedError = {
code: "invalid_request",
details: {
issues: [
{
path: ["path"],
reason: "invalid_format",
message: `Selected SQLite database is invalid (file is not a database): ${selected}`,
},
],
},
};
const corrupt = await cli.run({
arguments: ["inspect-sqlite-database", selected, "--json"],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(corrupt.exitCode).toBe(1);
expect(corrupt.json).toMatchObject(expectedError);
const response = await call("inspect_sqlite_database", {
path: selected,
});
expect(parseMcpToolError(response).error).toMatchObject(expectedError);
}
const valid = join(root, "valid.db");
const database = new DatabaseSync(valid);
try {
database.exec(
"CREATE TABLE present(value TEXT); CREATE VIRTUAL TABLE search USING fts5(body);",
);
} finally {
database.close();
}
const bytes = await readFile(valid);
const declaration = bytes.indexOf(Buffer.from("USING fts5"));
if (declaration < 0) throw new Error("Virtual declaration missing");
bytes.write("zzzz", declaration + Buffer.byteLength("USING "), "utf8");
await writeFile(valid, bytes);
const schema = await call("inspect_sqlite_database", { path: valid });
expect(schema.isError).not.toBe(true);
expect(schema.structuredContent).toMatchObject({
normalized_result: {
schema: {
tables: expect.arrayContaining([
expect.objectContaining({ name: "search_data", kind: "unknown" }),
]),
},
},
});
const ambiguous = await cli.run({
arguments: [
"inspect-sqlite-database",
valid,
"--table",
"search_data",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(ambiguous.exitCode).toBe(1);
expect(ambiguous.stdout).toContain("shadow table");
expect(
(
await call("inspect_sqlite_database", {
path: valid,
table: "search_data",
})
).isError,
).toBe(true);
const missing = await cli.run({
arguments: [
"inspect-sqlite-database",
valid,
"--table",
"absent",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(missing.exitCode).toBe(1);
expect(missing.stdout).toContain("absent");
expect(
(await call("inspect_sqlite_database", { path: valid, table: "absent" }))
.isError,
).toBe(true);
for (const input of [
{ path: valid, row_limit: 1 },
{ path: valid, table: "present", row_limit: 0 },
{ path: valid, query: "DELETE FROM present" },
]) {
expect(
(await call("inspect_sqlite_database", input)).isError,
JSON.stringify(input),
).toBe(true);
}
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"reports inventory projections beyond SQLite bind capacity through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory(
"rea-sqlite-inventory-capacity-",
);
const path = join(root, "selected.db");
const producer = new DatabaseSync(path);
let selectedColumns: number;
let engineColumnLimit: number;
try {
producer.exec(
"CREATE TABLE records(value TEXT); INSERT INTO records VALUES ('retained');",
);
engineColumnLimit = producer.limits.column;
selectedColumns =
Math.max(producer.limits.variableNumber, engineColumnLimit) + 1;
} finally {
producer.close();
}
const before = await sourceState(path);
const { call } = await connectLocalToolsMcp();
const control = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--schema-scope",
"inventory",
"--table",
"records",
"--column",
"value",
"--row-limit",
"1",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(control.exitCode, control.stdout + control.stderr).toBe(0);
expect(control.json).toMatchObject({
normalized_result: {
schema: { scope: "inventory" },
rows: {
columns: ["value"],
returned_rows: 1,
values: [[{ type: "text", value: "retained" }]],
},
},
});
const columns = Array.from({ length: selectedColumns }, () => "value");
// The framed fixture executes the compiled public CLI without the host's
// process-argument ceiling rejecting this many repeated flags first.
const result = await runFileCli(cli, {
arguments: [
"inspect-sqlite-database",
path,
"--schema-scope",
"inventory",
"--table",
"records",
...columns.flatMap((column) => ["--column", column]),
"--row-limit",
"1",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
const response = await call("inspect_sqlite_database", {
path,
schema_scope: "inventory",
table: "records",
columns,
row_limit: 1,
});
const expectedError = {
code: "resource_constraint",
category: "resource_constraint",
retryable: false,
details: {
resource: "capacity",
diagnostics: { selected_path: path },
reported_limits: {
selected_result_columns: selectedColumns,
engine_result_column_limit: engineColumnLimit,
},
},
};
expect(result.exitCode, result.stdout + result.stderr).toBe(1);
expect(result.json).toMatchObject(expectedError);
expect(parseMcpToolError(response).error).toMatchObject(expectedError);
expect(await sourceState(path)).toEqual(before);
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"reports unavailable application-collation rows with schema-only recovery through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-collation-public-");
const path = createSqliteApplicationCollationFixture(root);
const before = await sourceState(path);
const { call } = await connectLocalToolsMcp();
const schema = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--schema-scope",
"database",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
const schemaResponse = await call("inspect_sqlite_database", {
path,
schema_scope: "database",
});
const expectedSchema = {
subject: { local_path: path, digest: { sha256: digest(before.bytes) } },
normalized_result: {
rows: null,
schema: {
tables: [
{
name: "items",
without_rowid: true,
sql: expect.stringContaining("COLLATE APPLICATION_COLLATION"),
columns: [
{ name: "name", hidden: 0 },
{ name: "value", hidden: 0 },
{ name: "generated_value", hidden: 2 },
],
},
],
},
},
};
expect(schema.exitCode, schema.stdout + schema.stderr).toBe(0);
expect(schema.json).toMatchObject(expectedSchema);
expect(schemaResponse.isError).not.toBe(true);
expect(schemaResponse.structuredContent).toMatchObject(expectedSchema);
const result = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--table",
"items",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
const response = await call("inspect_sqlite_database", {
path,
table: "items",
});
const expectedError = {
code: "capability_unavailable",
retryable: false,
message: expect.stringMatching(
/rows of table "items".*(?:no query solution|no such collation sequence)/,
),
details: {
provider_id: "sqlite",
operation: "inspect_sqlite_database",
diagnostics: { selected_path: path },
},
remediation: {
action: expect.stringContaining("schema_scope: database"),
},
};
expect(result.exitCode, result.stdout + result.stderr).toBe(1);
expect(result.json).toMatchObject(expectedError);
const error = parseMcpToolError(response).error;
expect(error).toMatchObject(expectedError);
expect(error.remediation.action).not.toContain(
"Select columns whose values were read",
);
expect(await sourceState(path)).toEqual(before);
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"attributes an unevaluable generated column to its selection, not the readable database path",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-generated-");
const path = join(root, "hashes.db");
const producer = new DatabaseSync(path);
try {
// The producing application supplies a function REA's connection lacks.
producer.function(
"application_digest",
{ deterministic: true },
(value) => (typeof value === "string" ? `digest:${value}` : null),
);
producer.exec(`
CREATE TABLE t(a TEXT, b TEXT GENERATED ALWAYS AS (application_digest(a)) VIRTUAL);
INSERT INTO t(a) VALUES ('hello'), ('world');
`);
} finally {
producer.close();
}
const before = await readFile(path);
const { call } = await connectLocalToolsMcp();
const message = `SQLite cannot evaluate selected column(s) of table "t" on REA's read-only connection: "b": unknown function: application_digest()`;
const expectedError = {
code: "capability_unavailable",
retryable: false,
message,
details: { provider_id: "sqlite", reason: message },
remediation: {
action: expect.stringContaining(
'Select columns whose values were read: columns: ["a"] (CLI: one --column per name)',
),
},
};
for (const columns of [["b", "a"], undefined]) {
const result = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--table",
"t",
...(columns ?? []).flatMap((column) => ["--column", column]),
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(result.exitCode, result.stdout + result.stderr).toBe(1);
expect(result.json).toMatchObject(expectedError);
expect(result.stdout).not.toContain("invalid_format");
const response = await call("inspect_sqlite_database", {
path,
table: "t",
...(columns === undefined ? {} : { columns }),
});
expect(parseMcpToolError(response).error).toMatchObject(expectedError);
}
const base = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--table",
"t",
"--column",
"a",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(base.exitCode, base.stdout + base.stderr).toBe(0);
expect(base.json).toMatchObject({
normalized_result: { rows: { columns: ["a"], returned_rows: 2 } },
});
const schema = await call("inspect_sqlite_database", {
path,
schema_scope: "database",
});
expect(schema.isError).not.toBe(true);
expect(schema.structuredContent).toMatchObject({
normalized_result: {
rows: null,
schema: {
tables: [
{
name: "t",
sql: expect.stringContaining("application_digest(a)"),
columns: [{ name: "a" }, { name: "b", hidden: 2 }],
},
],
},
},
});
expect(Buffer.compare(await readFile(path), before)).toBe(0);
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"reads sqlite_schema rows through CLI and MCP in every schema scope",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-schema-table-");
const path = join(root, "selected.db");
const writer = new DatabaseSync(path);
try {
writer.exec("CREATE TABLE records(id INTEGER)");
} finally {
writer.close();
}
const original = await readFile(path);
const { call } = await connectLocalToolsMcp();
for (const schema_scope of [
"inventory",
"selected_table",
"database",
] as const) {
const result = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--table",
"sqlite_schema",
"--column",
"name",
"--column",
"type",
"--schema-scope",
schema_scope,
"--json",
],
});
expect(result.exitCode, result.stdout + result.stderr).toBe(0);
expect(result.json).toMatchObject({
normalized_result: {
schema: { scope: schema_scope },
rows: {
table: "sqlite_schema",
columns: ["name", "type"],
values: [
[
{ type: "text", value: "records" },
{ type: "text", value: "table" },
],
],
},
},
});
const response = await call("inspect_sqlite_database", {
path,
table: "sqlite_schema",
columns: ["name", "type"],
schema_scope,
});
expect(response.isError, JSON.stringify(response)).not.toBe(true);
expect(response.structuredContent).toEqual(result.json);
}
expect(Buffer.compare(await readFile(path), original)).toBe(0);
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"returns complete UTF-16 schemas through the worker and public CLI when JSON fits",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-large-catalog-");
for (const [encoding, scope] of [
["UTF-16le", "inventory"],
["UTF-16be", "database"],
] as const) {
const { path, names } = createSqliteLongNameFixture(root, encoding);
const response = await runFileCli(cli, {
arguments: [
"inspect-sqlite-database",
path,
"--schema-scope",
scope,
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(response.exitCode, response.stderr).toBe(0);
const evidence = parseEvidence(response.json);
const report = sqliteDatabaseSchema.parse(evidence.normalized_result);
expect(evidence.parameters.schema_scope).toBe(scope);
expect(report.database_encoding).toBe(encoding);
expect(report.schema).toMatchObject({ scope, completeness: "complete" });
expect(report.schema.tables).toHaveLength(names.length);
expect(report.schema.tables.map((table) => table.name)).toEqual(
expect.arrayContaining(names),
);
expect(
report.schema.tables.every(
(table) =>
table.columns_completeness ===
(scope === "inventory" ? "not_requested" : "complete"),
),
).toBe(true);
expect(report.rows).toBeNull();
}
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"keeps selected table metadata tied to exact schema-name bytes through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-invalid-name-");
const path = join(root, "selected.db");
const producer = new DatabaseSync(path);
try {
producer.exec(`
CREATE TABLE "col"(id INTEGER PRIMARY KEY, value TEXT) WITHOUT ROWID, STRICT;
INSERT INTO "col" VALUES(1, 'unrelated');
CREATE TABLE "�ol"(value TEXT);
INSERT INTO "�ol" VALUES('selected');
`);
} finally {
producer.close();
}
const bytes = await readFile(path);
let corruptedNameOccurrences = 0;
for (let index = 0; index < bytes.length - 3; index += 1) {
if (bytes.subarray(index, index + 3).equals(Buffer.from("col"))) {
bytes[index] = 0xff;
corruptedNameOccurrences += 1;
}
}
expect(corruptedNameOccurrences).toBeGreaterThanOrEqual(2);
await writeFile(path, bytes);
const native = new DatabaseSync(path, {
readOnly: true,
readBigInts: true,
});
try {
expect(native.prepare("PRAGMA integrity_check").get()).toEqual({
integrity_check: "ok",
});
const nativeTables = native
.prepare(
"SELECT hex(CAST(name AS BLOB)) AS name_hex, ncol, wr, strict FROM temp.pragma_table_list WHERE schema = 'main'",
)
.all();
expect(nativeTables).toEqual(
expect.arrayContaining([
{
name_hex: "EFBFBD6F6C",
ncol: 1n,
wr: 0n,
strict: 0n,
},
{
name_hex: "FF6F6C",
ncol: 2n,
wr: 1n,
strict: 1n,
},
]),
);
} finally {
native.close();
}
const cliResult = await cli.run({
arguments: [
"inspect-sqlite-database",
path,
"--table",
"�ol",
"--row-limit",
"1",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
const { call } = await connectLocalToolsMcp();
const mcpResult = await call("inspect_sqlite_database", {
path,
table: "�ol",
row_limit: 1,
});
expect(cliResult.exitCode, cliResult.stdout + cliResult.stderr).toBe(0);
expect(cliResult.json).toMatchObject({
normalized_result: {
schema: {
tables: [
expect.objectContaining({
name: "�ol",
without_rowid: false,
strict: false,
column_count: 1,
columns_completeness: "complete",
}),
],
},
rows: {
table: "�ol",
values: [[{ type: "text", value: "selected" }]],
},
},
});
expect(mcpResult.isError).not.toBe(true);
expect(mcpResult.structuredContent).toMatchObject({
normalized_result: {
schema: {
tables: [
expect.objectContaining({
name: "�ol",
without_rowid: false,
strict: false,
column_count: 1,
columns_completeness: "complete",
}),
],
},
rows: {
table: "�ol",
values: [[{ type: "text", value: "selected" }]],
},
},
});
},
);
cliTest.runIf(SQLITE_NATIVE_LIMITS_AVAILABLE)(
"folds ASCII trigger spelling without merging non-ASCII UTF-16 table names",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-sqlite-utf16-identity-");
const { call } = await connectLocalToolsMcp();
for (const encoding of ["UTF-16le", "UTF-16be"]) {
const path = join(root, `${encoding}.db`);
const producer = new DatabaseSync(path);
try {
producer.exec(`PRAGMA encoding = '${encoding}';
CREATE TABLE "Ł_table"(id INTEGER);
CREATE TABLE "š_table"(id INTEGER);
CREATE TRIGGER first_trigger AFTER INSERT ON "Ł_TABLE" BEGIN SELECT 1; END;
CREATE TRIGGER second_trigger AFTER INSERT ON "š_TABLE" BEGIN SELECT 1; END;`);
} finally {
producer.close();
}
const original = await readFile(path);
const result = await cli.run({
arguments: ["inspect-sqlite-database", path, "--json"],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(result.exitCode, result.stderr).toBe(0);
expect(result.json).toMatchObject({
normalized_result: {
schema: {
triggers: [
{ name: "first_trigger", table: "Ł_table" },
{ name: "second_trigger", table: "š_table" },
],
},
},
});
const response = await call("inspect_sqlite_database", { path });
expect(response.isError, JSON.stringify(response)).not.toBe(true);
expect(response.structuredContent).toEqual(result.json);
expect(Buffer.compare(await readFile(path), original)).toBe(0);
}
},
);