mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 18:52:48 +00:00
* fix(desktop): retain capture identity and cleanup ownership * fix(desktop): validate destinations and finish inputs before export * fix(desktop): preserve package ownership and apt trust boundaries * fix(sqlite): preserve observed facts and distinguish special real values * fix(desktop): bind computer files to data-object metadata * test(desktop): count the tagged negative-zero cell spelling SQLite view cells encode negative zero as a real object so it stays distinct from the text "-0". The byte budget follows that spelling. * fix(desktop): keep unresolved discovered state directories Canonicalizing an unreadable path or dangling symlink aborted capture before the inventory could record that the path was unknown. Those discovered directories now keep their absolute spelling in the scope. * test(desktop): keep the capture scope suite within its line limit The unresolved state-directory case lives beside the suite so the existing callback stays within the lint limit. * fix(desktop): retain cancellation through capture input cleanup * fix(desktop): avoid reserved Pydantic settings properties --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
115 lines
4.3 KiB
TypeScript
115 lines
4.3 KiB
TypeScript
import { parseAnalysisInput } from "../AnalysisInput.js";
|
|
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
|
import type { SqliteDatabasePort } from "./SqliteDatabasePort.js";
|
|
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
|
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
|
import type { Evidence, EvidenceSubjectTarget } from "../../domain/evidence.js";
|
|
import { createAnalysisExecutionEvidence } from "../../domain/analysisObservation.js";
|
|
import { ownedJsonValueSchema } from "../../domain/jsonValue.js";
|
|
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
|
import {
|
|
inspectSqliteDatabaseInputSchema,
|
|
sqliteDatabaseSchema,
|
|
type InspectSqliteDatabaseInput,
|
|
type SqliteDatabase,
|
|
SQLITE_ROW_LIMIT_DEFAULT,
|
|
DEFAULT_SQLITE_WORKER_TIMEOUT_MS,
|
|
sqliteSchemaHasObjectDeclaration,
|
|
sqliteSchemaScope,
|
|
sqliteInspectionParameters,
|
|
} from "../../domain/sqlite/sqliteDatabase.js";
|
|
import { err, type Result } from "../../domain/result.js";
|
|
|
|
const OPERATION = "inspect_sqlite_database";
|
|
|
|
const matchesSelectedSchema = (
|
|
schema: SqliteDatabase["schema"],
|
|
table: string,
|
|
): boolean =>
|
|
sqliteSchemaHasObjectDeclaration(table)
|
|
? schema.tables.length === 1 &&
|
|
schema.tables[0]?.name === table &&
|
|
schema.indexes.every((index) => index.table === table) &&
|
|
schema.triggers.every((trigger) => trigger.table === table) &&
|
|
schema.views.length === 0
|
|
: schema.tables.length === 0 &&
|
|
schema.indexes.length === 0 &&
|
|
schema.views.length === 0 &&
|
|
schema.triggers.length === 0;
|
|
|
|
const matchesSelectedDatabase = (
|
|
report: SqliteDatabase,
|
|
input: InspectSqliteDatabaseInput,
|
|
subject: EvidenceSubjectTarget,
|
|
): boolean =>
|
|
report.artifact.path === input.path &&
|
|
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" ||
|
|
(input.table !== undefined &&
|
|
matchesSelectedSchema(report.schema, input.table))) &&
|
|
(input.table === undefined
|
|
? report.rows === null
|
|
: report.rows?.table === input.table &&
|
|
report.rows.row_limit === (input.row_limit ?? SQLITE_ROW_LIMIT_DEFAULT) &&
|
|
report.rows.row_offset === (input.row_offset ?? 0) &&
|
|
(input.columns === undefined ||
|
|
(report.rows.columns.length === input.columns.length &&
|
|
report.rows.columns.every(
|
|
(name, index) => name === input.columns?.[index],
|
|
))));
|
|
|
|
/** Shared snapshot inspection and caller-selection validation for CLI and MCP. */
|
|
export class SqliteDatabaseService {
|
|
constructor(readonly provider: SqliteDatabasePort) {}
|
|
|
|
/** Await provider shutdown, retaining failed cleanup owners for retry. */
|
|
close(): Promise<void> {
|
|
return this.provider.close?.() ?? Promise.resolve();
|
|
}
|
|
|
|
/** Inspect an explicit database snapshot and preserve its original database/WAL identity. */
|
|
async inspect(
|
|
rawInput: unknown,
|
|
options?: ExecutionOptions,
|
|
): Promise<Result<Evidence, AnalysisError>> {
|
|
const input = parseAnalysisInput(
|
|
OPERATION,
|
|
rawInput,
|
|
inspectSqliteDatabaseInputSchema,
|
|
options?.signal,
|
|
);
|
|
if (!input.ok) return input;
|
|
const inspected = await this.provider.inspect(input.value, options);
|
|
if (!inspected.ok) return inspected;
|
|
const execution = inspected.value;
|
|
const report = sqliteDatabaseSchema.safeParse(execution.result);
|
|
if (
|
|
!report.success ||
|
|
execution.subject == null ||
|
|
!matchesSelectedDatabase(report.data, input.value, execution.subject)
|
|
)
|
|
return err(
|
|
new AnalysisOutputError(
|
|
OPERATION,
|
|
"SQLite adapter returned malformed data or changed the selected database identity/table/row limit/worker budget.",
|
|
),
|
|
);
|
|
return completeProviderEvidence(
|
|
createAnalysisExecutionEvidence(
|
|
OPERATION,
|
|
sqliteInspectionParameters(input.value),
|
|
{ ...execution, result: ownedJsonValueSchema.parse(report.data) },
|
|
"observed",
|
|
),
|
|
options?.signal,
|
|
);
|
|
}
|
|
}
|