{"record":{"id":"d8dde0a51f7f9f66","repo":"koala73/worldmonitor","slug":"clearkillgate-no-ramp-configured","errorCode":null,"errorMessage":"[clearKillGate] no ramp configured","messagePattern":"\\[clearKillGate\\] no ramp configured","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"convex/broadcast/rampRunner.ts","lineNumber":248,"sourceCode":"  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,\n      killGateReason: undefined,\n      lastRunStatus: `kill-gate-cleared: ${reason.slice(0, 200)}`,\n    });\n    return { ok: true };\n  },\n});\n\n/**\n * Last-resort soft-clear of a `partial-failure` status. STRONGLY DISPREFERRED;\n * use `recoverFromPartialFailure` instead in nearly every case.\n *\n * Naive clear is RISKY when the export already succeeded. Concrete failure\n * shape: `assignAndExportWave` stamped contacts with `waveLabel` AND created","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/broadcast/rampRunner.ts#L230-L266","documentation":"clearKillGate resets broadcastRampConfig.killGateTripped=false and clears the reason. It loads the singleton config via loadConfig and throws if no row exists, so clearing a gate on a non-existent ramp cannot silently succeed.","triggerScenarios":"Calling clearKillGate before initRamp, after abortRamp deleted the row, or against an unseeded Convex deployment.","commonSituations":"The ramp was aborted during incident triage but a recovery runbook step still calls clearKillGate; tooling pointed at the wrong Convex project.","solutions":["Preflight with getRampStatus; if {configured:false} there is no gate to clear — abort the runbook step.","If the config is intentionally gone, no action is needed (abortRamp already removed the tripped state).","Re-seed via initRamp if a new ramp is intended, then re-evaluate the gate."],"exampleFix":"// before\nawait ctx.runMutation(internal.broadcast.rampRunner.clearKillGate, { reason });\n\n// after\nconst status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});\nif (!status.configured) return { skipped: 'no ramp configured — nothing to clear' };\nawait ctx.runMutation(internal.broadcast.rampRunner.clearKillGate, { reason });","handlingStrategy":"validation","validationCode":"// Preflight before clearKillGate\nconst status = await ctx.runQuery(internal.broadcast.rampRunner.getRampStatus, {});\nif (!status.configured) {\n  return { skipped: 'no ramp configured — nothing to clear' };\n}","typeGuard":"function isConfigured(s: { configured: boolean }): s is { configured: true } {\n  return s.configured === true;\n}","tryCatchPattern":null,"preventionTips":["Run getRampStatus before clearKillGate to avoid operating on an absent config.","Note abortRamp already removes a tripped gate with the row — no separate clear needed after abort.","Keep recovery runbooks abort-aware so they no-op cleanly when the ramp is gone."],"tags":["convex","broadcast-ramp","state-missing","kill-gate"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}