{"record":{"id":"8e34eca050bce878","repo":"JuliusBrussee/caveman","slug":"at-least-one-sink-id-is-required","errorCode":null,"errorMessage":"at least one sink id is required","messagePattern":"at least one sink id is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/learn_simulate.go","lineNumber":73,"sourceCode":"\tfor _, sink := range plan.Sinks {\n\t\tbyID[sink.SinkID] = sink\n\t}\n\tunique := map[string]bool{}\n\tfor _, id := range sinkIDs {\n\t\tid = strings.TrimSpace(id)\n\t\tif id != \"\" {\n\t\t\tunique[id] = true\n\t\t}\n\t}\n\tids := make([]string, 0, len(unique))\n\tfor id := range unique {\n\t\tif _, ok := byID[id]; !ok {\n\t\t\treturn LearnSimulation{}, fmt.Errorf(\"sink %q not found in current learn plan\", id)\n\t\t}\n\t\tids = append(ids, id)\n\t}\n\tif len(ids) == 0 {\n\t\treturn LearnSimulation{}, fmt.Errorf(\"at least one sink id is required\")\n\t}\n\tsort.Strings(ids)\n\n\t// One owner per config file/family. Prefer broader measured weight, then sink\n\t// id, so overlap refusal is stable regardless of CLI argument order.\n\towners := map[string]string{}\n\tfor _, id := range ids {\n\t\tsink := byID[id]\n\t\tkey := simulationOverlapKey(sink)\n\t\tif key == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\towner, exists := owners[key]\n\t\tif !exists || sink.TokensPerTurn > byID[owner].TokensPerTurn ||\n\t\t\t(sink.TokensPerTurn == byID[owner].TokensPerTurn && id < owner) {\n\t\t\towners[key] = id\n\t\t}\n\t}","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/internal/store/learn_simulate.go#L55-L91","documentation":"simulateLearnPlan requires at least one sink id after deduplication of the requested selection. An empty selection means there is nothing to simulate, so it returns an explicit error instead of an empty simulation report. The check happens after collecting unique ids, so passing duplicates that all dedupe to nothing still triggers it.","triggerScenarios":"Calling BuildLearnSimulationFiltered with an empty or nil sink id slice, or with a selection that contains no valid entries after dedup.","commonSituations":"Scripting the CLI with a variable that ends up empty; passing only whitespace/blank ids; wiring automation that forwards an optional flag with no value.","solutions":["Pass at least one valid sink id to the simulation call","Default the CLI to the full plan when no explicit selection is given","Guard your script: exit early with usage help if the selection list is empty"],"exampleFix":"// before\nids := os.Getenv(\"SINKS\")\nstore.BuildLearnSimulationFiltered(plan, strings.Split(ids, \",\"))\n// after\nids := os.Getenv(\"SINKS\")\nif strings.TrimSpace(ids) == \"\" {\n    return fmt.Errorf(\"set SINKS to at least one sink id\")\n}\nstore.BuildLearnSimulationFiltered(plan, strings.Split(ids, \",\"))","handlingStrategy":"validation","validationCode":"cleaned := make([]string, 0, len(ids))\nfor _, id := range ids {\n    if id = strings.TrimSpace(id); id != \"\" { cleaned = append(cleaned, id) }\n}\nif len(cleaned) == 0 { return fmt.Errorf(\"supply at least one sink id to simulate\") }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate and dedupe the sink selection before invoking the simulation","Default empty selections to the full plan in your wrapper","Treat the selection flag as required in CLI help and shell completions","Fail fast in scripts when a selection env var is unset or blank"],"tags":["cli","validation","learn-simulate"],"backgroundTag":"empty-required-field","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}