{"record":{"id":"a7c0f2157679fa31","repo":"elsa-workflows/elsa-core","slug":"a-proposal-conversation-id-is-required","errorCode":null,"errorMessage":"A proposal conversation ID is required.","messagePattern":"A proposal conversation ID is required\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIProposalStore.cs","lineNumber":135,"sourceCode":"\n    private static bool BelongsToTenant(string? storedTenantId, string? requestedTenantId) =>\n        string.Equals(NormalizeTenantId(storedTenantId), NormalizeTenantId(requestedTenantId), StringComparison.Ordinal);\n\n    private static string NormalizeTenantId(string? tenantId) => tenantId ?? \"\";\n\n    private static void ValidateUserOwnership(AIProposalRecord record, AIProposal proposal)\n    {\n        if (!string.IsNullOrWhiteSpace(record.CreatedBy) && !string.Equals(record.CreatedBy, proposal.CreatedBy, StringComparison.Ordinal))\n            throw new InvalidOperationException(\"Cannot overwrite an AI proposal that belongs to another user.\");\n    }\n\n    private static void Validate(AIProposal proposal)\n    {\n        if (string.IsNullOrWhiteSpace(proposal.Id))\n            throw new ArgumentException(\"A proposal ID is required.\", nameof(proposal));\n\n        if (string.IsNullOrWhiteSpace(proposal.ConversationId))\n            throw new ArgumentException(\"A proposal conversation ID is required.\", nameof(proposal));\n\n        if (string.IsNullOrWhiteSpace(proposal.CreatedBy))\n            throw new ArgumentException(\"A proposal creator is required.\", nameof(proposal));\n    }\n}\n","sourceCodeStart":117,"sourceCodeEnd":141,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIProposalStore.cs#L117-L141","documentation":"The proposal store's Validate method requires a non-whitespace ConversationId on every AIProposal. Proposals are scoped to their parent conversation; a missing ConversationId breaks that linkage and tenant/user ownership checks, so ArgumentException (paramName 'proposal') is thrown.","triggerScenarios":"Calling SaveAsync with an AIProposal that has a valid Id but a null/empty/whitespace ConversationId (this check runs after the Id check and before the CreatedBy check).","commonSituations":"Creating a proposal outside the context of a conversation; a mapper that omits ConversationId; API callers posting proposals without the parent conversation reference; refactors renaming the field and missing the assignment.","solutions":["Set proposal.ConversationId to the parent conversation's Id before SaveAsync.","Ensure the proposal is created through a flow that has the conversation context available.","Validate in your service layer that ConversationId is present and the referenced conversation exists."],"exampleFix":"// before\nvar proposal = new AIProposal { Id = id, CreatedBy = user }; // ConversationId missing\nawait proposalStore.SaveAsync(proposal);\n// after\nvar proposal = new AIProposal { Id = id, ConversationId = conversation.Id, CreatedBy = user };\nawait proposalStore.SaveAsync(proposal);","handlingStrategy":"validation","validationCode":"if (string.IsNullOrWhiteSpace(proposal.ConversationId)) throw new ArgumentException(\"A proposal ConversationId is required before SaveAsync.\");\n// optionally: ensure the conversation exists\n// if (!await dbContext.Conversations.AnyAsync(c => c.Id == proposal.ConversationId)) throw new InvalidOperationException(\"Parent conversation not found.\");","typeGuard":"static bool HasConversation(AIProposal p) => !string.IsNullOrWhiteSpace(p.ConversationId);","tryCatchPattern":"try { await proposalStore.SaveAsync(proposal); }\ncatch (ArgumentException ex) when (ex.ParamName == \"proposal\" && ex.Message.Contains(\"conversation ID\")) { /* attach the proposal to a conversation before saving */ }","preventionTips":["Create proposals only within a conversation-scoped code path.","Require ConversationId in the API/DTO layer.","Verify the parent conversation exists before saving.","Keep proposal construction close to conversation handling so the reference is always available."],"tags":["ef-core","ai-proposals","validation","argument"],"backgroundTag":"missing-required-argument","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}