{"record":{"id":"139734933e8580d3","repo":"koala73/worldmonitor","slug":"initramp-rampcurve-must-be-non-empty","errorCode":null,"errorMessage":"[initRamp] rampCurve must be non-empty","messagePattern":"\\[initRamp\\] rampCurve must be non-empty","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"convex/broadcast/rampRunner.ts","lineNumber":166,"sourceCode":"    // Required as a pair when `waveLabelOffset > 0` (operational\n    // signal that the ramp is resuming after manual waves). The very\n    // first wave ever (offset=0) is exempt because there is no prior.\n    seedLastWaveBroadcastId: v.optional(v.string()),\n    seedLastWaveSentAt: v.optional(v.number()),\n    seedLastWaveLabel: v.optional(v.string()),\n    seedLastWaveSegmentId: v.optional(v.string()),\n    seedLastWaveAssigned: v.optional(v.number()),\n    // Locale filter — when true, pickWaveAction excludes contacts with\n    // non-English locale (canonical via `users.localePrimary`, fallback\n    // via email-TLD heuristic for legacy waitlist registrants without a\n    // users row). Default false to preserve byte-identical behavior for\n    // existing ramps. Operators opt in for wave-8+ deliberately. Run\n    // `_dryRunNonEnglishExclusion` first to inspect impact before flipping.\n    excludeNonEnglish: v.optional(v.boolean()),\n  },\n  handler: async (ctx, args) => {\n    if (args.rampCurve.length === 0) {\n      throw new Error(\"[initRamp] rampCurve must be non-empty\");\n    }\n    if (args.rampCurve.some((n) => !Number.isInteger(n) || n <= 0)) {\n      throw new Error(\"[initRamp] rampCurve entries must be positive integers\");\n    }\n    const offset = args.waveLabelOffset ?? 0;\n    const hasSeedBroadcast = !!args.seedLastWaveBroadcastId;\n    const hasSeedSentAt = typeof args.seedLastWaveSentAt === \"number\";\n    if (hasSeedBroadcast !== hasSeedSentAt) {\n      throw new Error(\n        \"[initRamp] seedLastWaveBroadcastId and seedLastWaveSentAt must be provided together.\",\n      );\n    }\n    if (offset > 0 && !hasSeedBroadcast) {\n      throw new Error(\n        `[initRamp] waveLabelOffset=${offset} signals resumption after manual waves; seedLastWaveBroadcastId + seedLastWaveSentAt are required so the first cron tick can apply the kill-gate against the prior wave. Pass them, or set waveLabelOffset=0 to start a fresh ramp.`,\n      );\n    }\n    const existing = await ctx.db","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/broadcast/rampRunner.ts#L148-L184","documentation":"Thrown by initRamp when args.rampCurve is an empty array. The ramp curve defines the per-wave recipient counts that escalate the broadcast, and an empty curve means the ramp has no tiers to progress through — the config would be a no-op. This is the first validation check in initRamp.","triggerScenarios":"Calling initRamp with rampCurve: []; passing an array that was dynamically constructed and ended up empty (e.g. a generator that produced no entries); a config typo where the curve was omitted/forgotten.","commonSituations":"A ramp-config script defaulted the curve to [] and wasn't filled in; the curve was read from a config file with a missing/empty rampCurve key; test invocation forgot to populate the curve.","solutions":["Provide a non-empty rampCurve, e.g. [100, 250, 500, 1000] escalating per wave.","Validate the curve is non-empty in the ramp-config script before calling initRamp.","Confirm the config source actually contains the intended curve values."],"exampleFix":"// before\nawait initRamp(ctx, { rampCurve: [], waveLabelPrefix: \"w\" });\n// after\nawait initRamp(ctx, { rampCurve: [100, 250, 500, 1000], waveLabelPrefix: \"w\" });","handlingStrategy":"validation","validationCode":"if (!Array.isArray(rampCurve) || rampCurve.length === 0) {\n  throw new Error(\"rampCurve must be a non-empty array\");\n}\nawait initRamp(ctx, { rampCurve, waveLabelPrefix });","typeGuard":"function isNonEmptyCurve(c: unknown): c is number[] {\n  return Array.isArray(c) && c.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Validate rampCurve is non-empty in the config-builder script.","Unit-test initRamp with an empty array to confirm it rejects.","Keep ramp curves in a versioned config file."],"tags":["convex","broadcast","ramp","validation","config"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}