{"record":{"id":"3e0385a1d9fafd20","repo":"elsa-workflows/elsa-core","slug":"a-proposal-creator-is-required","errorCode":null,"errorMessage":"A proposal creator is required.","messagePattern":"A proposal creator is required\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIProposalStore.cs","lineNumber":138,"sourceCode":"\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":120,"sourceCodeEnd":141,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.AI.Persistence.EFCore/Stores/EFCoreAIProposalStore.cs#L120-L141","documentation":"EFCoreAIProposalStore.Validate rejects an AIProposal that is missing required identity fields before writing it to the database. In this case proposal.CreatedBy is null or whitespace, meaning the proposal does not record who created it, which the store treats as a mandatory audit field. The exception is an ArgumentException naming the proposal parameter, thrown from Validate during SaveAsync.","triggerScenarios":"Calling EFCoreAIProposalStore.SaveAsync(proposal, ct) where proposal.CreatedBy is null, empty, or whitespace, while proposal.Id and proposal.ConversationId are set.","commonSituations":"Constructing an AIProposal manually in code or tests without assigning CreatedBy; mapping from an API/DTO that omits the creator field; code paths that copy a proposal but drop the creator during transformation.","solutions":["Set proposal.CreatedBy to the acting user/service identity before calling SaveAsync.","If the creator is unknown, use an explicit sentinel such as \"system\" rather than leaving it empty.","Add upstream validation at the endpoint/handler level so unassigned proposals are rejected with a clear message before reaching the store."],"exampleFix":"// before\nvar proposal = new AIProposal { Id = id, ConversationId = conversationId, Payload = payload };\nawait store.SaveAsync(proposal, ct);\n\n// after\nvar proposal = new AIProposal { Id = id, ConversationId = conversationId, Payload = payload, CreatedBy = currentUser.Id };\nawait store.SaveAsync(proposal, ct);","handlingStrategy":"validation","validationCode":"if (proposal is null) throw new ArgumentNullException(nameof(proposal));\nif (string.IsNullOrWhiteSpace(proposal.Id)) throw new ArgumentException(\"A proposal ID is required.\", nameof(proposal));\nif (string.IsNullOrWhiteSpace(proposal.ConversationId)) throw new ArgumentException(\"A proposal conversation ID is required.\", nameof(proposal));\nif (string.IsNullOrWhiteSpace(proposal.CreatedBy)) throw new ArgumentException(\"A proposal creator is required.\", nameof(proposal));","typeGuard":"bool HasCreator(Elsa.AI.Models.AIProposal p) => !string.IsNullOrWhiteSpace(p?.CreatedBy);","tryCatchPattern":"try\n{\n    await store.SaveAsync(proposal, ct);\n}\ncatch (ArgumentException ex) when (ex.ParamName == \"proposal\")\n{\n    logger.LogError(ex, \"Invalid AI proposal: {Message}\", ex.Message);\n}","preventionTips":["Always populate CreatedBy from the authenticated principal or an explicit 'system' identity when constructing proposals.","Centralize proposal construction in a factory that enforces required fields.","Validate proposals at API boundaries before they reach persistence code."],"tags":["ef-core","validation","missing-field","ai-proposals"],"backgroundTag":"empty-required-field","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}