{"record":{"id":"178717c4807769a5","repo":"nexu-io/open-design","slug":"proposal-patch-is-required","errorCode":null,"errorMessage":"proposal patch is required","messagePattern":"proposal patch is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/daemon/src/automation-proposals.ts","lineNumber":85,"sourceCode":"}\n\nexport async function createAutomationProposal(\n  dataDir: string,\n  input: CreateAutomationEvolutionProposalRequest & {\n    id?: string;\n    status?: AutomationProposalStatus;\n  },\n): Promise<AutomationEvolutionProposal> {\n  const now = new Date().toISOString();\n  if (!input || typeof input !== 'object') throw new Error('proposal body is required');\n  if (typeof input.title !== 'string' || !input.title.trim()) {\n    throw new Error('proposal title is required');\n  }\n  if (typeof input.summary !== 'string' || !input.summary.trim()) {\n    throw new Error('proposal summary is required');\n  }\n  if (!input.patch || typeof input.patch !== 'object') {\n    throw new Error('proposal patch is required');\n  }\n  const status =\n    input.status && VALID_STATUSES.has(input.status)\n      ? input.status\n      : 'pending-review';\n  const proposal: AutomationEvolutionProposal = {\n    id:\n      typeof input.id === 'string' && input.id.trim()\n        ? input.id.trim()\n        : `proposal_${randomUUID()}`,\n    title: input.title.trim(),\n    summary: input.summary.trim(),\n    targetKind: input.targetKind,\n    action: input.action,\n    status,\n    reviewPolicy: input.reviewPolicy ?? 'always',\n    createdAt: now,\n    updatedAt: now,","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/automation-proposals.ts#L67-L103","documentation":"Thrown by createAutomationProposal() when input.patch is falsy or not an object. patch carries the proposed change (format + after + diffSummary) and is the load-bearing field — a proposal without a patch has nothing to apply.","triggerScenarios":"POST to the proposals endpoint with patch omitted, null, an array, a string, or any non-object. Fires after title and summary checks pass.","commonSituations":"Client sending diffSummary as the top-level field instead of nesting it under patch; caller passing a JSON string for patch instead of an object; agent emitting a proposal before serializing its patch.","solutions":["Supply patch as an object with at least format ('json' or 'markdown'), after (string), and diffSummary.","If patch was serialized to a string, JSON.parse it before calling.","Mirror the patch shape produced by buildMemoryProposalPatch / buildDesignSystemProposalMarkdown in automation-ingestions.ts."],"exampleFix":"// before\nawait createAutomationProposal(dataDir, { title: 'T', summary: 'S', patch: '{\"format\":\"json\"}' });\n// after\nawait createAutomationProposal(dataDir, {\n  title: 'T', summary: 'S',\n  patch: { format: 'json', after: '{\"body\":\"...\"}', diffSummary: 'create entry' }\n});","handlingStrategy":"type-guard","validationCode":"if (!input.patch || typeof input.patch !== 'object') {\n  throw new Error('proposal patch is required');\n}","typeGuard":"function isPatchObject(value: unknown): value is { format?: string; after?: string; diffSummary?: string } {\n  return !!value && typeof value === 'object' && !Array.isArray(value);\n}","tryCatchPattern":null,"preventionTips":["Nest diffSummary under patch, not at the top level.","Use JSON.stringify for json-format patches rather than template literals.","Mirror the patch shapes from automation-ingestions.ts."],"tags":["automation","proposals","validation","api","daemon"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}