mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
feat(sqlite): inspect offline snapshots with selective evidence (#2271)
Add offline SQLite database/WAL inspection through the shared CLI/MCP. Use compact inventory, selected-table schema, exact ordered columns and bounded row samples while preserving source identity and lossless values. Retain completed Evidence through late cancellation and private-root cleanup failures, bound concurrent inspections, and recheck journal validity after acquisition. Reuse prepared CI runtime artifacts across supported Node lines. Co-authored-by: Y0UW1SH <29498139+Y0UW1SH@users.noreply.github.com> Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)
This commit is contained in:
1 parent
916ac97bf9
commit
aeee5074d1
59 files changed
+5757
-24
No files matched your search
@@ -0,0 +1,165 @@
|
||||
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import {
|
||||
AnalysisError,
|
||||
type AnalysisCapturedOutput,
|
||||
} from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisAccessDeniedError,
|
||||
AnalysisArtifactChangedError,
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
AnalysisResourceConstraintError,
|
||||
AnalysisTimeoutError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
||||
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
||||
import { OwnedCommandFailure } from "../process/OwnedCommand.js";
|
||||
import type { ProviderProcessSnapshot } from "../process/ProviderProcess.js";
|
||||
import { SQLITE_PROVIDER_IDENTITY } from "../domain/sqlite/sqliteDatabase.js";
|
||||
import { SQLITE_DATABASE_LIMITS } from "./SqliteDatabaseLimits.js";
|
||||
|
||||
/** Preserve diagnostics independently of worker exit and private-root cleanup. */
|
||||
export const capturedSqliteOutput = (
|
||||
snapshot: ProviderProcessSnapshot,
|
||||
): AnalysisCapturedOutput => ({
|
||||
stdout: snapshot.stdout.text,
|
||||
stderr: snapshot.stderr.text,
|
||||
truncated: snapshot.diagnosticTruncated === true,
|
||||
});
|
||||
|
||||
/** Preserve artifact, caller, process, resource and output failure meanings. */
|
||||
export const sqliteDatabaseFailure = (
|
||||
cause: unknown,
|
||||
path: string,
|
||||
phase: string,
|
||||
signal?: AbortSignal,
|
||||
): AnalysisError => {
|
||||
const operation = "inspect_sqlite_database";
|
||||
if (cause instanceof AnalysisError) return cause;
|
||||
if (cause instanceof ArtifactReaderFailure) {
|
||||
if (cause.cleanup !== undefined)
|
||||
return new ProviderCleanupError(
|
||||
SQLITE_PROVIDER_IDENTITY.id,
|
||||
cause.cleanup.resources,
|
||||
{
|
||||
reason: cause.cleanup.reason,
|
||||
previous_error:
|
||||
cause.cause === undefined
|
||||
? null
|
||||
: projectAnalysisError(
|
||||
signal?.aborted
|
||||
? new AnalysisCancelledError(operation, {
|
||||
cause: cause.cause,
|
||||
})
|
||||
: sqliteDatabaseFailure(cause.cause, path, phase),
|
||||
),
|
||||
},
|
||||
{ operation, cause },
|
||||
);
|
||||
if (cause.reason === "io" && cause.cause !== undefined)
|
||||
return sqliteDatabaseFailure(cause.cause, path, phase, signal);
|
||||
if (cause.reason === "integrity")
|
||||
return new AnalysisArtifactChangedError(operation, path, cause.message, {
|
||||
cause,
|
||||
});
|
||||
if (cause.reason === "cancelled")
|
||||
return new AnalysisCancelledError(operation, { cause });
|
||||
return new AnalysisInputError(operation, { cause }, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: cause.reason === "limit" ? "out_of_range" : "invalid_format",
|
||||
message: cause.message,
|
||||
},
|
||||
]);
|
||||
}
|
||||
if (cause instanceof Error && cause.name === "AbortError")
|
||||
return new AnalysisCancelledError(operation, { cause });
|
||||
if (cause instanceof OwnedCommandFailure) {
|
||||
const projected = ownedCommandFailure(cause);
|
||||
if (projected !== undefined) return projected;
|
||||
}
|
||||
if (phase === "artifact-read" && cause instanceof Error && "code" in cause) {
|
||||
if (cause.code === "EACCES" || cause.code === "EPERM")
|
||||
return new AnalysisAccessDeniedError(operation, path, cause.code, {
|
||||
cause,
|
||||
});
|
||||
if (cause.code === "ENOENT" || cause.code === "ENOTDIR")
|
||||
return new AnalysisInputError(operation, { cause }, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_value",
|
||||
message: `Selected database file set could not be read (${String(cause.code)}): ${path}`,
|
||||
},
|
||||
]);
|
||||
}
|
||||
return new ProviderAdapterError(SQLITE_PROVIDER_IDENTITY.id, operation, {
|
||||
cause,
|
||||
diagnostics: {
|
||||
phase,
|
||||
path,
|
||||
reason: cause instanceof Error ? cause.message : String(cause),
|
||||
...(cause instanceof OwnedCommandFailure && cause.snapshot !== null
|
||||
? {
|
||||
failure_kind: cause.reason,
|
||||
exit_code: cause.snapshot.exitCode ?? null,
|
||||
signal: cause.snapshot.signal ?? null,
|
||||
captured_output: { ...capturedSqliteOutput(cause.snapshot) },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const ownedCommandFailure = (
|
||||
cause: OwnedCommandFailure,
|
||||
): AnalysisError | undefined => {
|
||||
const operation = "inspect_sqlite_database";
|
||||
const capturedOutput =
|
||||
cause.snapshot === null ? undefined : capturedSqliteOutput(cause.snapshot);
|
||||
const options = {
|
||||
cause,
|
||||
...(capturedOutput === undefined ? {} : { capturedOutput }),
|
||||
};
|
||||
if (cause.cleanupFailure !== null)
|
||||
return new ProviderCleanupError(
|
||||
SQLITE_PROVIDER_IDENTITY.id,
|
||||
cause.resources,
|
||||
{
|
||||
reason: cause.cleanupFailure,
|
||||
previous_error: {
|
||||
failure_kind: cause.reason,
|
||||
message: cause.message,
|
||||
},
|
||||
...(capturedOutput === undefined
|
||||
? {}
|
||||
: { captured_output: { ...capturedOutput } }),
|
||||
},
|
||||
{ operation, cause },
|
||||
);
|
||||
if (cause.reason === "cancelled")
|
||||
return new AnalysisCancelledError(operation, options);
|
||||
if (cause.reason === "timeout")
|
||||
return new AnalysisTimeoutError(
|
||||
operation,
|
||||
SQLITE_DATABASE_LIMITS.timeoutMs,
|
||||
options,
|
||||
);
|
||||
if (cause.reason === "output-limit")
|
||||
return new AnalysisOutputError(operation, cause.message, options);
|
||||
if (
|
||||
cause.snapshot?.stderr.text.includes("JavaScript heap out of memory") ===
|
||||
true
|
||||
)
|
||||
return new AnalysisResourceConstraintError(
|
||||
operation,
|
||||
"memory",
|
||||
"Owned SQLite worker reported JavaScript heap exhaustion.",
|
||||
{
|
||||
javascript_heap_bytes: 256 * 1024 * 1024,
|
||||
},
|
||||
options,
|
||||
);
|
||||
return undefined;
|
||||
};
|
||||
Reference in new issue
Block a user