{"record":{"id":"8811b067a4510a9b","repo":"koala73/worldmonitor","slug":"resumeramp-kill-gate-is-tripped-clearkillgate-f","errorCode":null,"errorMessage":"[resumeRamp] kill-gate is tripped; clearKillGate first after investigating.","messagePattern":"\\[resumeRamp\\] kill-gate is tripped; clearKillGate first after investigating\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"convex/broadcast/rampRunner.ts","lineNumber":235,"sourceCode":"});\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  },\n});\n\nexport const clearKillGate = internalMutation({\n  args: { reason: v.string() },\n  handler: async (ctx, { reason }) => {\n    const row = await loadConfig(ctx);\n    if (!row) throw new Error(\"[clearKillGate] no ramp configured\");\n    if (!row.killGateTripped) {\n      return { ok: true, noop: true };\n    }\n    await ctx.db.patch(row._id, {\n      killGateTripped: false,","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/broadcast/rampRunner.ts#L217-L253","documentation":"resumeRamp refuses to flip active=true when broadcastRampConfig.killGateTripped is true. The kill-gate is tripped by the runner when a prior wave's bounce or complaint rate exceeded the configured threshold (DEFAULT_BOUNCE_KILL_THRESHOLD / DEFAULT_COMPLAINT_KILL_THRESHOLD). Resuming would re-enable sending while sender reputation is at risk, so the operator must explicitly clear the gate after investigating.","triggerScenarios":"Calling resumeRamp after the daily runner recorded a kill-gate trip; calling it after manually setting killGateTripped=true; resuming while the last wave's Resend stats still show out-of-threshold bounce/complaint.","commonSituations":"A wave hit a hard-bounce spike (stale list) or complaint spike and the runner auto-tripped the gate; the operator wants to resume but has not yet diagnosed root cause.","solutions":["Investigate the trip: check getRampStatus().killGateReason and the last wave's Resend bounce/complaint stats.","Resolve the list/content issue (suppress hard bounces, fix content causing complaints), then call clearKillGate({reason}) with a documented justification.","Then call resumeRamp to re-enable the cron."],"exampleFix":"// before\nawait ctx.runMutation(internal.broadcast.rampRunner.resumeRamp, {});\n\n// after\nconst status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});\nif (status.configured && status.killGateTripped) {\n  throw new Error(`Kill-gate tripped: ${status.killGateReason}. Investigate, then clearKillGate.`);\n}\nawait ctx.runMutation(internal.broadcast.rampRunner.resumeRamp, {});","handlingStrategy":"validation","validationCode":"// Preflight before resumeRamp — block if kill-gate is tripped\nconst status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});\nif (status.configured && status.killGateTripped) {\n  throw new Error(`Kill-gate tripped (${status.killGateReason}). Investigate, then clearKillGate before resumeRamp.`);\n}","typeGuard":"function killGateIsTripped(s: { configured: boolean; killGateTripped?: boolean }): boolean {\n  return s.configured === true && s.killGateTripped === true;\n}","tryCatchPattern":null,"preventionTips":["Always inspect killGateReason and the last wave's Resend bounce/complaint stats before clearing.","Never auto-resume: require an explicit operator decision after a kill-gate trip.","Tune bounceKillThreshold/complaintKillThreshold deliberately; defaults exist for sender reputation protection."],"tags":["convex","broadcast-ramp","kill-gate","sender-reputation","safety-gate"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}