{"record":{"id":"f0bfb00fa3d740ea","repo":"koala73/worldmonitor","slug":"recordwavesent-no-ramp-configured","errorCode":null,"errorMessage":"[_recordWaveSent] no ramp configured","messagePattern":"\\[_recordWaveSent\\] no ramp configured","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"convex/broadcast/rampRunner.ts","lineNumber":782,"sourceCode":"/**\n * Internal mutation that the action calls to atomically advance the tier +\n * record a successful wave-send. Validates that the lease still belongs to\n * this runId AND clears all pending-progress markers.\n */\nexport const _recordWaveSent = internalMutation({\n  args: {\n    runId: v.string(),\n    expectedCurrentTier: v.number(),\n    newTier: v.number(),\n    waveLabel: v.string(),\n    broadcastId: v.string(),\n    segmentId: v.string(),\n    assigned: v.number(),\n    sentAt: v.number(),\n  },\n  handler: async (ctx, args) => {\n    const row = await loadConfig(ctx);\n    if (!row) throw new Error(\"[_recordWaveSent] no ramp configured\");\n    if (row.currentTier !== args.expectedCurrentTier) {\n      throw new Error(\n        `[_recordWaveSent] tier moved underneath us: expected ${args.expectedCurrentTier}, found ${row.currentTier}. Refusing to overwrite.`,\n      );\n    }\n    if (row.pendingRunId !== args.runId) {\n      // The lease changed under us — operator force-released it, or\n      // recoverFromPartialFailure cleared it. We must NOT advance the tier;\n      // bubble to Convex auto-Sentry so ops can investigate.\n      throw new Error(\n        `[_recordWaveSent] lease lost: expected runId=${args.runId}, found ${row.pendingRunId ?? \"<cleared>\"}. Refusing to advance tier — investigate what cleared the lease.`,\n      );\n    }\n    await ctx.db.patch(row._id, {\n      currentTier: args.newTier,\n      lastWaveLabel: args.waveLabel,\n      lastWaveBroadcastId: args.broadcastId,\n      lastWaveSegmentId: args.segmentId,","sourceCodeStart":764,"sourceCodeEnd":800,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/broadcast/rampRunner.ts#L764-L800","documentation":"_recordWaveSent is the terminal success commit: it advances currentTier and records lastWave* metadata after a wave is sent. It loads the config via loadConfig and throws if no row exists, because committing a wave against an aborted ramp would be inconsistent.","triggerScenarios":"The runner reached the final commit step but abortRamp deleted the config between run start and _recordWaveSent; or the run executes in an unseeded deployment.","commonSituations":"Operator aborted during an in-flight send; deployment mismatch; parallel recovery cleared the config.","solutions":["Do not retry — the config is gone by intent. Surface to Sentry; verify whether the Resend broadcast actually sent.","If accidental abort, re-seed via initRamp with the correct currentTier/seed so the next wave is the right one (avoid double-sending the just-sent wave).","Serialize operator aborts against live runs."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Runner-side: re-check config existence before the terminal commit\nconst row = await ctx.db.query('broadcastRampConfig').withIndex('by_key', (q) => q.eq('key', 'current')).first();\nif (!row) {\n  return { aborted: 'config-gone' }; // a broadcast may have sent — reconcile manually\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ctx.runMutation(internal.broadcast.rampRunner._recordWaveSent, { runId, expectedCurrentTier, newTier, ... });\n} catch (e) {\n  console.error('[runner] _recordWaveSent failed:', (e as Error).message);\n  throw e; // ops must verify whether the Resend broadcast actually sent\n}","preventionTips":["Never abort the ramp during an in-flight send.","If this fires, check Resend delivery state for the broadcast before re-seeding.","Re-seed with the correct currentTier to avoid re-sending the just-sent wave."],"tags":["convex","broadcast-ramp","state-missing","lease","runner-internal"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}