{"record":{"id":"fef84a3b9b03620e","repo":"nexu-io/open-design","slug":"subquery-must-have-at-least-one-source","errorCode":null,"errorMessage":"SubQuery must have at least one source","messagePattern":"SubQuery must have at least one source","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"design-templates/last30days/scripts/lib/schema.py","lineNumber":53,"sourceCode":"    reasoning_provider: Literal[\"gemini\", \"openai\", \"xai\", \"local\"]\n    planner_model: str\n    rerank_model: str\n    x_search_backend: Literal[\"xai\", \"bird\"] | None = None\n\n\n@dataclass(frozen=True)\nclass SubQuery:\n    \"\"\"Planner-emitted retrieval unit.\"\"\"\n\n    label: str\n    search_query: str\n    ranking_query: str\n    sources: list[str]\n    weight: float = 1.0\n\n    def __post_init__(self) -> None:\n        if not self.sources:\n            raise ValueError(\"SubQuery must have at least one source\")\n        if self.weight <= 0:\n            raise ValueError(f\"SubQuery weight must be positive, got {self.weight}\")\n\n\n@dataclass\nclass QueryPlan:\n    \"\"\"Planner output.\"\"\"\n\n    intent: str\n    freshness_mode: str\n    cluster_mode: str\n    raw_topic: str\n    subqueries: list[SubQuery]\n    source_weights: dict[str, float]\n    notes: list[str] = field(default_factory=list)\n\n\n@dataclass","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/design-templates/last30days/scripts/lib/schema.py#L35-L71","documentation":"SubQuery is a frozen dataclass representing one planner-emitted retrieval unit. Its __post_init__ enforces that sources is non-empty, because a retrieval unit with no source has nowhere to dispatch and would fall straight through stream_results to 'Unsupported source'. The guard fails the construction early instead of at retrieval time.","triggerScenarios":"Constructing SubQuery(..., sources=[]) or sources=None. Typically the planner LLM emitted a subquery whose sources list was stripped/emptied by post-processing, or a hand-built plan forgot the field.","commonSituations":"Planner JSON parse that filtered out all unsupported sources and left an empty list. Refactor that builds SubQuery from a dict missing the 'sources' key. Test fixtures that defaulted sources to [].","solutions":["Ensure the planner always emits at least one supported source per subquery.","When filtering subquery sources, drop the whole subquery if the filter empties its sources list.","Validate the planner JSON shape (sources is a non-empty list of known names) before constructing SubQuery objects."],"exampleFix":"# before\nSubQuery(label='t', search_query='q', ranking_query='q', sources=[])\n\n# after\nSubQuery(label='t', search_query='q', ranking_query='q', sources=['reddit', 'hackernews'])","handlingStrategy":"validation","validationCode":"def build_subquery(label, search_query, ranking_query, sources, weight=1.0):\n    if not sources:\n        raise ValueError(f\"subquery {label!r} needs at least one source\")\n    return SubQuery(label, search_query, ranking_query, list(sources), weight=weight)","typeGuard":"def has_sources(sources) -> bool:\n    return isinstance(sources, (list, tuple)) and len(sources) > 0","tryCatchPattern":"null","preventionTips":["Validate planner JSON: each subquery's sources must be a non-empty list of known source names.","When filtering sources, drop the whole subquery if its sources become empty.","Unit-test the planner parser against fixtures with missing/empty sources."],"tags":["validation","dataclass","planner","invariant"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}