Harden PRD page coverage and migration audit

This commit is contained in:
ZacharyZhang-NY committed 2026-05-21 09:04:36 -04:00
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),