@@ -440,6 +440,26 @@ function runMigrations(db) {
440440 db . prepare ( 'UPDATE schema_version SET version = 10' ) . run ( ) ;
441441 } ) ( ) ;
442442 }
443+
444+ if ( current < 11 ) {
445+ db . transaction ( ( ) => {
446+ // ── Migration v11: cap error_logs.log_snippet at 16 KB ──────────────────
447+ // The inline log_snippet column historically stored up to 512 KB per
448+ // failure, bloating audit.db. The full log already lives on disk in the
449+ // per-run audit-*.log file, so the DB only needs a readable snippet for
450+ // the copy-failure UX. Going forward insertErrorLog() caps at 16384 chars
451+ // from the tail; this migration truncates any legacy oversized rows to
452+ // match. substr(s, length(s) - 16384 + 1) keeps the LAST 16384 chars,
453+ // mirroring the `.slice(-16384)` tail semantics. Bounded fast UPDATE —
454+ // safe on the boot path (no file I/O here).
455+ db . prepare ( `
456+ UPDATE error_logs
457+ SET log_snippet = substr(log_snippet, length(log_snippet) - 16384 + 1)
458+ WHERE log_snippet IS NOT NULL AND length(log_snippet) > 16384
459+ ` ) . run ( ) ;
460+ db . prepare ( 'UPDATE schema_version SET version = 11' ) . run ( ) ;
461+ } ) ( ) ;
462+ }
443463}
444464
445465// ─── Run Mutations ───────────────────────────────────────────────────────────
@@ -906,7 +926,7 @@ export function getRunStats(runId, which) {
906926 * @param {string } opts.stage - 'handshake'|'session'|'speedtest'|'wallet'|'rpc'|'other'
907927 * @param {string } [opts.error_code]
908928 * @param {string } [opts.error_message]
909- * @param {string } [opts.log_snippet] - up to 512 KB of log context
929+ * @param {string } [opts.log_snippet] - a readable snippet of log context (capped at 16 KB)
910930 * @returns {number } inserted id
911931 */
912932export function insertErrorLog ( {
@@ -917,10 +937,14 @@ export function insertErrorLog({
917937 log_snippet = null ,
918938} , which ) {
919939 const db = getDb ( which ) ;
920- // Persist the full per-node tester log (capped at 512 KB to bound row
921- // size). Pipeline already trims to ~512 KB from the tail; this is the
922- // floor cap so a misbehaving caller can't blow the row.
923- const snippet = log_snippet ? log_snippet . slice ( - 524288 ) : null ;
940+ // Store only a readable snippet of the per-node tester log (capped at 16 KB
941+ // = 16384 chars from the tail). The FULL log is persisted to disk in the
942+ // per-run audit-*.log file; the DB column exists purely to power the
943+ // copy-failure UX, so a bounded tail is all we need. Production callers
944+ // already pre-truncate to ~4 KB via pipeline.js (_sanitizeSnippet /
945+ // _MAX_SNIPPET); this slice is the backstop so a misbehaving caller can't
946+ // blow up the row and bloat audit.db.
947+ const snippet = log_snippet ? log_snippet . slice ( - 16384 ) : null ;
924948 const info = db . prepare ( `
925949 INSERT INTO error_logs (result_id, stage, error_code, error_message, log_snippet, captured_at)
926950 VALUES (@result_id, @stage, @error_code, @error_message, @log_snippet, @captured_at)
0 commit comments