{"record":{"id":"95319139fd6ad688","repo":"koala73/worldmonitor","slug":"pauseramp-no-ramp-configured","errorCode":null,"errorMessage":"[pauseRamp] no ramp configured","messagePattern":"\\[pauseRamp\\] no ramp configured","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"convex/broadcast/rampRunner.ts","lineNumber":223,"sourceCode":"      lastWaveBroadcastId: args.seedLastWaveBroadcastId,\n      lastWaveSentAt: args.seedLastWaveSentAt,\n      lastWaveLabel: args.seedLastWaveLabel,\n      lastWaveSegmentId: args.seedLastWaveSegmentId,\n      lastWaveAssigned: args.seedLastWaveAssigned,\n      // Default-FALSE on insert when arg omitted. Per the design doc:\n      // explicit, never silently true — existing-ramp byte-identical\n      // behavior is the default; operators opt in for wave-8+.\n      excludeNonEnglish: args.excludeNonEnglish ?? false,\n    });\n    return { ok: true };\n  },\n});\n\nexport const pauseRamp = internalMutation({\n  args: {},\n  handler: async (ctx) => {\n    const row = await loadConfig(ctx);\n    if (!row) throw new Error(\"[pauseRamp] no ramp configured\");\n    await ctx.db.patch(row._id, { active: false });\n    return { ok: true, prevActive: row.active };\n  },\n});\n\nexport const resumeRamp = internalMutation({\n  args: {},\n  handler: async (ctx) => {\n    const row = await loadConfig(ctx);\n    if (!row) throw new Error(\"[resumeRamp] no ramp configured\");\n    if (row.killGateTripped) {\n      throw new Error(\n        \"[resumeRamp] kill-gate is tripped; clearKillGate first after investigating.\",\n      );\n    }\n    await ctx.db.patch(row._id, { active: true });\n    return { ok: true };\n  },","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/broadcast/rampRunner.ts#L205-L241","documentation":"pauseRamp is a Convex internalMutation that sets broadcastRampConfig.active=false. It reads the singleton config row via loadConfig(ctx), which queries the broadcastRampConfig table by key='current'. If no row exists the handler throws rather than no-op'ing, so an operator cannot silently 'pause' a ramp that was never seeded and believe it took effect.","triggerScenarios":"Calling the pauseRamp mutation before initRamp has ever run; calling it after abortRamp (which deletes the row); calling it from a cron/automation in a fresh Convex deployment or preview that was never seeded.","commonSituations":"New or staging Convex project that was never initialized; CI pointing at the wrong CONVEX_DEPLOYMENT; an operator ran abortRamp during incident triage and a still-scheduled job fires pauseRamp afterward.","solutions":["Preflight with getRampStatus; if it returns {configured:false}, run initRamp first to seed the config row.","If the ramp was aborted, re-run initRamp with the original rampCurve and seedLastWave* fields to recreate the row, then pauseRamp.","Confirm the calling client/cron targets the correct Convex deployment (CONVEX_DEPLOYMENT / VITE_CONVEX_URL) that holds the seeded config."],"exampleFix":"// before\nawait ctx.runMutation(internal.broadcast.rampRunner.pauseRamp, {});\n\n// after\nconst status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});\nif (!status.configured) throw new Error('Cannot pause: ramp not configured. Run initRamp first.');\nawait ctx.runMutation(internal.broadcast.rampRunner.pauseRamp, {});","handlingStrategy":"validation","validationCode":"// Preflight before pauseRamp\nconst status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});\nif (!status.configured) {\n  throw new Error('Ramp not configured — run initRamp before pauseRamp.');\n}","typeGuard":"type RampStatus =\n  | { configured: false }\n  | { configured: true; active: boolean; killGateTripped: boolean; currentTier: number; rampCurve: number[] };\n\nfunction isConfigured(s: RampStatus): s is Extract<RampStatus, { configured: true }> {\n  return s.configured === true;\n}","tryCatchPattern":null,"preventionTips":["Always preflight admin mutations with getRampStatus and branch on .configured.","Seed every Convex deployment (dev/preview/prod) with initRamp as part of provisioning.","Make pauseRamp calls idempotent in runbooks by checking status first."],"tags":["convex","broadcast-ramp","state-missing","operator-error"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}