{"record":{"id":"c6333e63811aa790","repo":"HKUDS/Vibe-Trading","slug":"source-and-schedule-must-be-objects","errorCode":null,"errorMessage":"source and schedule must be objects","messagePattern":"source and schedule must be objects","errorType":"validation","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"agent/src/scheduled_research/service.py","lineNumber":128,"sourceCode":"    return channel, target, None, \"当前会话\"\n\n\ndef build_job_from_draft(\n    draft: Mapping[str, Any],\n    *,\n    session_id: str | None = None,\n    now_ms: int | None = None,\n) -> ScheduledResearchJob:\n    \"\"\"Validate the public draft and build a persisted-model job.\"\"\"\n    now = int(time.time() * 1000) if now_ms is None else now_ms\n    title = str(draft.get(\"title\") or \"\").strip()\n    if not title:\n        raise ValueError(\"title is required\")\n    source = draft.get(\"source\")\n    schedule_spec = draft.get(\"schedule\")\n    delivery_spec = draft.get(\"delivery\") or {\"mode\": \"in_app\"}\n    if not isinstance(source, Mapping) or not isinstance(schedule_spec, Mapping):\n        raise ValueError(\"source and schedule must be objects\")\n    if not isinstance(delivery_spec, Mapping):\n        raise ValueError(\"delivery must be an object\")\n\n    source_type = str(source.get(\"kind\") or \"prompt\")\n    playbook_slug = None\n    config: dict[str, Any] = {}\n    if source_type == \"prompt\":\n        prompt = str(source.get(\"prompt\") or \"\").strip()\n        if not prompt:\n            raise ValueError(\"source.prompt is required\")\n    elif source_type == \"playbook\":\n        playbook_slug = str(source.get(\"playbook_slug\") or \"\").strip()\n        if not playbook_slug:\n            raise ValueError(\"source.playbook_slug is required\")\n        playbook = get_playbook(playbook_slug)\n        variables = source.get(\"variables\") or {}\n        if not isinstance(variables, Mapping):\n            raise ValueError(\"source.variables must be an object\")","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/HKUDS/Vibe-Trading/blob/80ffdda44c5c4db0dd84d70e051cca591cea67df/agent/src/scheduled_research/service.py#L110-L146","documentation":"The draft's 'source' and 'schedule' fields must both be JSON objects (Mappings). One of them is missing, null, or a scalar/array, so the builder cannot read kind/prompt or expression/timezone from it.","triggerScenarios":"Sending draft with source as a string prompt instead of {kind:'prompt',prompt:...}; schedule as a cron string instead of {expression:...}; either field omitted or null.","commonSituations":"API shape mismatch after version upgrade; clients simplifying the nested schema; docs examples that show flattened forms.","solutions":["Wrap values in objects: source={\"kind\":\"prompt\",\"prompt\":\"...\"} and schedule={\"expression\":\"0 9 * * *\"}","Check the current draft schema in service.build_job_from_draft","Add a client-side schema validator before submit"],"exampleFix":"// before\n{\"source\": \"summarize HN\", \"schedule\": \"0 9 * * *\"}\n// after\n{\"source\": {\"kind\": \"prompt\", \"prompt\": \"summarize HN\"}, \"schedule\": {\"expression\": \"0 9 * * *\"}}","handlingStrategy":"type-guard","validationCode":"from collections.abc import Mapping\nassert isinstance(draft.get(\"source\"), Mapping) and isinstance(draft.get(\"schedule\"), Mapping)","typeGuard":"from collections.abc import Mapping\n\ndef draft_source_schedule_valid(d: dict) -> bool:\n    return isinstance(d.get(\"source\"), Mapping) and isinstance(d.get(\"schedule\"), Mapping)","tryCatchPattern":"try:\n    build_job_from_draft(draft)\nexcept ValueError as e:\n    return schema_error(str(e))  # map to 400 for the client","preventionTips":["Use a typed schema (pydantic/zod) for the draft payload","Keep clients and server on the same draft schema version"],"tags":["scheduled-research","validation","schema"],"backgroundTag":"schema-validation-failed","analyzedSha":"80ffdda44c5c4db0dd84d70e051cca591cea67df","analyzedAt":"2026-08-28T12:46:38.989Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}