From 9073ec6a22f7f99e2da5448bdd38637cd24745c9 Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:38:28 -0700 Subject: [PATCH] fix(selfhost): recognize review_audit in the exporter's no-source-tables guard The "does this database have any reporting signal at all" fail-safe guard still listed only pull_requests/advisories/review_targets/ ai_usage_events -- stale since #3535 made review_audit a genuine dependency of the verdict computation. In practice pull_requests already dominates the check on any real deployment, so this was cosmetic, not a live bug, but a database with only review_audit present would have been misclassified as empty. Add it to both the Postgres and SQLite variants of the guard. Flagged as a non-blocking nit on #3535's own review. --- scripts/export-grafana-reporting-db.sh | 6 ++- test/unit/selfhost-grafana-reporting.test.ts | 45 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/scripts/export-grafana-reporting-db.sh b/scripts/export-grafana-reporting-db.sh index bd5b661129..7da775f148 100644 --- a/scripts/export-grafana-reporting-db.sh +++ b/scripts/export-grafana-reporting-db.sh @@ -187,7 +187,8 @@ if pg_enabled; then if ! pg_table_exists "pull_requests" && ! pg_table_exists "advisories" && ! pg_table_exists "review_targets" && - ! pg_table_exists "ai_usage_events"; then + ! pg_table_exists "ai_usage_events" && + ! pg_table_exists "review_audit"; then if [ -s "$OUT_DB" ]; then rm -f "$TMP_DB" "$TMP_DB-wal" "$TMP_DB-shm" echo "reporting export skipped: no reporting source tables in Postgres; preserving last good $OUT_DB" >&2 @@ -349,7 +350,8 @@ fi if ! source_table_exists "pull_requests" && ! source_table_exists "advisories" && ! source_table_exists "review_targets" && - ! source_table_exists "ai_usage_events"; then + ! source_table_exists "ai_usage_events" && + ! source_table_exists "review_audit"; then if [ -s "$OUT_DB" ]; then rm -f "$TMP_DB" "$TMP_DB-wal" "$TMP_DB-shm" echo "reporting export skipped: no reporting source tables in $APP_DB; preserving last good $OUT_DB" >&2 diff --git a/test/unit/selfhost-grafana-reporting.test.ts b/test/unit/selfhost-grafana-reporting.test.ts index 9d8199bad0..963b03e151 100644 --- a/test/unit/selfhost-grafana-reporting.test.ts +++ b/test/unit/selfhost-grafana-reporting.test.ts @@ -394,6 +394,51 @@ esac expect(sqlite(outDb, "SELECT status || '|' || (verdict IS NULL) FROM review_targets WHERE repo='JSONbored/gittensory' AND number=4001;")).toBe("manual|1"); }); + it("a source DB with ONLY review_audit (no pull_requests/review_targets/ai_usage_events) is recognized as having real data -- the exporter runs a fresh pass instead of preserving a stale last-good snapshot", () => { + const root = tmpRoot(); + const appDb = join(root, "app.sqlite"); + const outDb = join(root, "reporting.sqlite"); + + // Seed a legitimate "last good" output from a normal run, so a wrongly-triggered skip would be observable + // (the old snapshot would survive untouched instead of being replaced by a fresh, empty pass). + sqlite(appDb, ` + CREATE TABLE pull_requests ( + repo_full_name TEXT NOT NULL, number INTEGER NOT NULL, title TEXT NOT NULL, state TEXT NOT NULL, + author_login TEXT, merged_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL + ); + INSERT INTO pull_requests (repo_full_name, number, title, state, author_login, merged_at, created_at, updated_at) + VALUES ('JSONbored/gittensory', 9001, 'stale last-good PR', 'open', 'JSONbored', NULL, '2026-07-01T00:00:00Z', '2026-07-01T00:00:00Z'); + + CREATE TABLE review_audit ( + id TEXT NOT NULL, target_id TEXT NOT NULL, event_type TEXT NOT NULL, decision TEXT, + source TEXT NOT NULL, created_at TEXT NOT NULL + ); + INSERT INTO review_audit (id, target_id, event_type, decision, source, created_at) + VALUES ('g0', 'JSONbored/gittensory#9001', 'gate_decision', 'hold', 'gittensory-native', '2026-07-01T00:00:00Z'); + `); + runExporter(root, appDb, outDb); + expect(sqlite(outDb, "SELECT count(*) FROM review_targets;")).toBe("1"); + + // Now point at a fresh source DB containing ONLY review_audit -- no pull_requests, review_targets, or + // ai_usage_events at all. + const onlyAuditDb = join(root, "only-audit.sqlite"); + sqlite(onlyAuditDb, ` + CREATE TABLE review_audit ( + id TEXT NOT NULL, target_id TEXT NOT NULL, event_type TEXT NOT NULL, decision TEXT, + source TEXT NOT NULL, created_at TEXT NOT NULL + ); + INSERT INTO review_audit (id, target_id, event_type, decision, source, created_at) + VALUES ('g1', 'JSONbored/gittensory#9002', 'gate_decision', 'hold', 'gittensory-native', '2026-07-05T00:00:00Z'); + `); + // Must not throw: a wrongly-triggered "no reporting source tables" skip exits non-zero when a last-good + // snapshot already exists (see the seeded run above). + expect(() => runExporter(root, onlyAuditDb, outDb)).not.toThrow(); + + // The stale PR from the seeded run is GONE -- proving this was a genuine fresh pass (pull_requests doesn't + // exist in the only-review_audit source, so nothing populates review_targets), not a preserved snapshot. + expect(sqlite(outDb, "SELECT count(*) FROM review_targets;")).toBe("0"); + }); + it("falls back to legacy review_targets when the current PR cache is absent", () => { const root = tmpRoot(); const appDb = join(root, "app.sqlite");