Harden PRD page coverage and migration audit
This commit is contained in:
1 parent
83a51c6881
commit
f107ecba31
12 files changed
+235
-25
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { createHash } from "node:crypto";
|
||||
import { DatabaseSync, type StatementSync } from "node:sqlite";
|
||||
import type { CreateMigrationReportInput } from "../../shared/commands";
|
||||
import { createId } from "../../shared/ids";
|
||||
@@ -37,13 +38,35 @@ export class MigrationService {
|
||||
.filter((table) => availableTables.has(table))
|
||||
.map((table) => [table, countRows(source.prepare(`select count(*) as count from ${table}`))]),
|
||||
);
|
||||
const tableChecksums = Object.fromEntries(
|
||||
LEGACY_TABLES
|
||||
.filter((table) => availableTables.has(table))
|
||||
.map((table) => [table, tableChecksum(source, table)]),
|
||||
);
|
||||
const idMaps = Object.fromEntries(
|
||||
LEGACY_TABLES
|
||||
.filter((table) => availableTables.has(table))
|
||||
.map((table) => [table, tableIds(source, table)]),
|
||||
);
|
||||
const missingTables = LEGACY_TABLES.filter((table) => !availableTables.has(table));
|
||||
const failures = missingTables.map((table) => ({ table, reason: "missing_table" }));
|
||||
const reportId = createId("mig");
|
||||
const now = new Date().toISOString();
|
||||
this.db.prepare(`
|
||||
insert into migration_reports(id, org_id, source_ref, report_json, created_at)
|
||||
values (?, ?, ?, ?, ?)
|
||||
`).run(reportId, input.orgId, input.sourceDatabasePath, JSON.stringify({ tableCounts, missingTables }), now);
|
||||
`).run(reportId, input.orgId, input.sourceDatabasePath, JSON.stringify({
|
||||
tableCounts,
|
||||
tableChecksums,
|
||||
idMaps,
|
||||
missingTables,
|
||||
failures,
|
||||
rollbackPack: {
|
||||
sourceDatabasePath: input.sourceDatabasePath,
|
||||
capturedAt: now,
|
||||
restoreStrategy: "legacy_snapshot_restore",
|
||||
},
|
||||
}), now);
|
||||
this.audit.record({
|
||||
orgId: input.orgId,
|
||||
entityType: "migration_report",
|
||||
@@ -63,3 +86,16 @@ export class MigrationService {
|
||||
function countRows(statement: StatementSync): number {
|
||||
return (statement.get() as { count: number }).count;
|
||||
}
|
||||
|
||||
function tableChecksum(source: DatabaseSync, table: string): string {
|
||||
const hash = createHash("sha256");
|
||||
const rows = source.prepare(`select * from ${table} order by rowid`).all();
|
||||
for (const row of rows) hash.update(JSON.stringify(row));
|
||||
return hash.digest("hex");
|
||||
}
|
||||
|
||||
function tableIds(source: DatabaseSync, table: string): string[] {
|
||||
const columns = source.prepare(`pragma table_info(${table})`).all() as Array<{ name: string }>;
|
||||
if (!columns.some((column) => column.name === "id")) return [];
|
||||
return source.prepare(`select id from ${table} order by id`).all().map((row) => String((row as { id: unknown }).id));
|
||||
}
|
||||
@@ -71,6 +71,8 @@ export class SnapshotService {
|
||||
skillBindings,
|
||||
skillRuntimeSnapshots: list(this.db, "skill_runtime_snapshots", "created_at desc"),
|
||||
socialAccounts: list(this.db, "social_accounts", "created_at asc"),
|
||||
socialAccountGrants: list(this.db, "social_account_grants", "created_at asc"),
|
||||
browserActions: list(this.db, "browser_actions", "created_at desc"),
|
||||
policyRules: list(this.db, "policy_rules", "created_at asc"),
|
||||
migrationReports: list(this.db, "migration_reports", "created_at desc"),
|
||||
auditEvents: list<AppSnapshot["auditEvents"][number]>(this.db, "audit_events", "created_at desc").slice(0, 200),
|
||||
|
||||
Reference in new issue
Block a user