{"record":{"id":"ae75a6e94b75a02d","repo":"elsa-workflows/elsa-core","slug":"a-proposal-id-is-required","errorCode":null,"errorMessage":"A proposal ID is required.","messagePattern":"A proposal ID is required\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIProposalStore.cs","lineNumber":132,"sourceCode":"\n    private static TEnum ParseEnum<TEnum>(string value, TEnum defaultValue) where TEnum : struct =>\n        Enum.TryParse<TEnum>(value, ignoreCase: true, out var result) ? result : defaultValue;\n\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":114,"sourceCodeEnd":141,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIProposalStore.cs#L114-L141","documentation":"The proposal store's Validate method requires a non-whitespace Id before persisting an AIProposal. The Id is the primary key of AIProposalRecord; ArgumentException (paramName 'proposal') is thrown when it is missing so the store never inserts a record without an identifier.","triggerScenarios":"Calling SaveAsync with an AIProposal whose Id is null, empty, or whitespace — this is the first check, so it fires before the ConversationId and CreatedBy checks.","commonSituations":"Manually constructed AIProposal without Id; a generator/strategy returning null IDs; JSON deserialization dropping the Id field; tests creating proposals via object initializers missing the Id.","solutions":["Assign proposal.Id (e.g. Guid.NewGuid().ToString()) before SaveAsync.","Fix the ID-generation strategy or mapper so Id is always populated.","Add a pre-save guard in your service to reject proposals without an ID."],"exampleFix":"// before\nvar proposal = new AIProposal { ConversationId = cid, CreatedBy = user };\nawait proposalStore.SaveAsync(proposal);\n// after\nvar proposal = new AIProposal { Id = Guid.NewGuid().ToString(), ConversationId = cid, CreatedBy = user };\nawait proposalStore.SaveAsync(proposal);","handlingStrategy":"validation","validationCode":"if (string.IsNullOrWhiteSpace(proposal.Id)) throw new ArgumentException(\"A proposal ID is required before SaveAsync.\");","typeGuard":"static bool HasId(AIProposal p) => !string.IsNullOrWhiteSpace(p.Id);","tryCatchPattern":"try { await proposalStore.SaveAsync(proposal); }\ncatch (ArgumentException ex) when (ex.ParamName == \"proposal\" && ex.Message.Contains(\"proposal ID\")) { /* generate/assign an ID and retry once */ }","preventionTips":["Generate Id in the proposal factory or constructor.","Validate proposal fields before calling the store.","Verify mappers/deserializers populate Id.","Prefer Guid-based IDs to avoid deterministic collisions."],"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"}