{"record":{"id":"8879639c7de173f9","repo":"nexu-io/open-design","slug":"proposal-summary-is-required","errorCode":null,"errorMessage":"proposal summary is required","messagePattern":"proposal summary is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/daemon/src/automation-proposals.ts","lineNumber":82,"sourceCode":"): Promise<AutomationEvolutionProposal | null> {\n  const proposals = await listAutomationProposals(dataDir, { status: 'all' });\n  return proposals.find((proposal) => proposal.id === id) ?? null;\n}\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,","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/automation-proposals.ts#L64-L100","documentation":"Thrown by createAutomationProposal() when input.summary is missing or whitespace-only. summary is required because it is the human-readable rationale shown in the review UI and stored on the proposal record.","triggerScenarios":"POST to the proposals endpoint with summary omitted, empty, whitespace-only, or non-string. Fires after the title check and before the patch check.","commonSituations":"Agent submitted a proposal with only a title; client form dropped the summary field; reviewer-facing rationale was left for later and never filled.","solutions":["Provide a non-empty summary string describing why the proposal exists.","For ingested sources, reuse the auto-generated 'Create a memory-tree entry from ...' summary shape.","Validate summary.trim() on the client before submitting."],"exampleFix":"// before\nawait createAutomationProposal(dataDir, { title: 'T', summary: '', patch: { format: 'json', after: '{}' } });\n// after\nawait createAutomationProposal(dataDir, { title: 'T', summary: 'Captures pricing feedback from the sales channel.', patch: { format: 'json', after: '{}' } });","handlingStrategy":"validation","validationCode":"if (typeof input.summary !== 'string' || !input.summary.trim()) {\n  throw new Error('proposal summary is required');\n}","typeGuard":"function hasNonEmptySummary(value: unknown): value is string {\n  return typeof value === 'string' && value.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Always populate summary with the reviewer-facing rationale.","Reuse the ingestion pipeline's auto-generated summary shape for agent proposals.","Validate summary.trim() client-side."],"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"}