Context
Audit #320 identified three confirmed issues in the backfill cron (src/app/api/cron/backfill/route.ts), agreed on by all 5 reviewers.
Task 1: Add missing plot title (E3)
processPlotChained() (line 261) destructures title from event args but never writes it to the DB row. The direct indexer (src/app/api/index/plot/route.ts:118) correctly writes title: title || "".
Fix: Add title: title || "" to the PlotInsert row in processPlotChained() around line 270.
Task 2: Log failed events instead of silently skipping (E6, E7)
When IPFS content can't be fetched or hash doesn't match, processPlotChained() and processStorylineCreated() silently return. The cursor still advances to toBlock, permanently skipping those events.
Fix:
- Create a
backfill_failures Supabase table: (id, tx_hash, log_index, block_number, event_name, storyline_id, reason, created_at)
- When IPFS fetch fails or hash mismatches, insert a row into
backfill_failures instead of silently returning
- Count these as errors in the response JSON
- Cursor can still advance (to avoid infinite loops), but failures are now visible and recoverable
Migration file needed: supabase/migrations/XXXXXX_backfill_failures.sql
Task 3: Fail closed on missing CRON_SECRET in production (D2)
verifyCron() returns true when CRON_SECRET is not set. In production, this means anyone can trigger backfill.
Fix:
function verifyCron(req: Request): boolean {
const secret = process.env.CRON_SECRET;
if (!secret) {
return process.env.NODE_ENV !== "production";
}
const authHeader = req.headers.get("authorization");
return authHeader === `Bearer ${secret}`;
}
Acceptance Criteria
Context
Audit #320 identified three confirmed issues in the backfill cron (
src/app/api/cron/backfill/route.ts), agreed on by all 5 reviewers.Task 1: Add missing plot title (E3)
processPlotChained()(line 261) destructurestitlefrom event args but never writes it to the DB row. The direct indexer (src/app/api/index/plot/route.ts:118) correctly writestitle: title || "".Fix: Add
title: title || ""to thePlotInsertrow inprocessPlotChained()around line 270.Task 2: Log failed events instead of silently skipping (E6, E7)
When IPFS content can't be fetched or hash doesn't match,
processPlotChained()andprocessStorylineCreated()silently return. The cursor still advances totoBlock, permanently skipping those events.Fix:
backfill_failuresSupabase table:(id, tx_hash, log_index, block_number, event_name, storyline_id, reason, created_at)backfill_failuresinstead of silently returningMigration file needed:
supabase/migrations/XXXXXX_backfill_failures.sqlTask 3: Fail closed on missing CRON_SECRET in production (D2)
verifyCron()returnstruewhenCRON_SECRETis not set. In production, this means anyone can trigger backfill.Fix:
Acceptance Criteria
titlefield matching direct indexer behaviorbackfill_failurestableverifyCron()rejects requests whenCRON_SECRETis unset in productionbackfill_failurestable includednpm run buildandnpm run typecheckpass