{"record":{"id":"bd4170c7ee56c8bc","repo":"we-promise/sure","slug":"source-citation-is-required","errorCode":null,"errorMessage":"source citation is required","messagePattern":"source citation is required","errorType":"exception","errorClass":"Provenance::Citation::InvalidError","httpStatus":null,"severity":"error","filePath":"app/models/provenance/citation.rb","lineNumber":43,"sourceCode":"    # match here, folding the grade into the citation text and yielding an\n    # ungraded source — silently losing the reliability the caller supplied.\n    FORMAT = /\\A(?<estimated>estimated:\\s)?(?<text>.+?)(?:\\s?\\(grade:\\s*(?<grade>[ABC])\\))?\\z/\n    # Matched separately so \"(grade: D)\" fails loudly instead of folding into the\n    # citation text and passing as an ungraded — but plausible-looking — source.\n    GRADE_SUFFIX = /\\(grade:\\s*(?<grade>[^)]*)\\)\\s*\\z/\n    ESTIMATED_MARKER = /\\Aestimated\\s*:/i\n\n    class InvalidError < StandardError; end\n\n    attr_reader :raw, :text, :grade\n\n    class << self\n      # Returns a Citation, or raises InvalidError with a message written for the\n      # caller that has to fix it (an agent, usually).\n      def parse!(raw)\n        value = raw.to_s.strip\n\n        raise InvalidError, \"source citation is required\" if value.blank?\n        raise InvalidError, \"source citation must be #{MAX_LENGTH} characters or fewer\" if value.length > MAX_LENGTH\n\n        if value.match?(ESTIMATED_MARKER) && !value.start_with?(ESTIMATED_PREFIX)\n          raise InvalidError, \"estimated values must start with the exact prefix #{ESTIMATED_PREFIX.inspect}\"\n        end\n\n        if (suffix = value.match(GRADE_SUFFIX)) && !GRADES.include?(suffix[:grade])\n          raise InvalidError, \"reliability grade must be one of #{GRADES.join(\", \")} (got #{suffix[:grade].inspect})\"\n        end\n\n        match = value.match(FORMAT)\n        raise InvalidError, \"source citation does not match #{grammar}\" unless match\n\n        text = match[:text].to_s.strip\n        if text.length < MIN_TEXT_LENGTH\n          raise InvalidError, \"source citation must name the document it came from\"\n        end\n","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provenance/citation.rb#L25-L61","documentation":"Provenance::Citation.parse! raises Provenance::Citation::InvalidError 'source citation is required' when the input, after to_s.strip, is blank (nil, empty string, whitespace). A citation is mandatory provenance metadata: every agent-recorded value must name where it came from, so absence is rejected at the write boundary.","triggerScenarios":"Calling Citation.parse!(nil), parse!(\"\"), or parse!(\"   \"); an agent/LLM omitting the source field when writing a ledger value; a form submitted with the citation field left empty.","commonSituations":"Agent pipeline where the model returned no source; optional form field treated as skippable; default nil passed through a data-import path.","solutions":["Supply a citation naming the document, e.g. 'Private bank statement 2026-03-31, line 12'.","If the value is an estimate, use 'estimated: ... (grade: C)' format.","Use Provenance::Citation.valid?(value) to check before the write and reject with a user-facing validation message.","Make the citation field required upstream (model validation or form required attribute)."],"exampleFix":"# before\nProvenance::Citation.parse!(params[:source])\n\n# after\nraise ArgumentError, \"source is required\" if params[:source].to_s.strip.empty?\nProvenance::Citation.parse!(params[:source])","handlingStrategy":"validation","validationCode":"Provenance::Citation.valid?(value) # false when blank; check before writing","typeGuard":"def valid_citation?(value) = !value.to_s.strip.empty? && Provenance::Citation.valid?(value)","tryCatchPattern":"begin\n  Provenance::Citation.parse!(source)\nrescue Provenance::Citation::InvalidError => e\n  errors.add(:source, e.message) # feed message back to the agent/user\nend","preventionTips":["Make the citation field required in forms and model validations.","In agent prompts, demand a source string for every emitted value and reject outputs without one.","Prefer Citation.valid? to probe, and use parse! only at the write boundary."],"tags":["provenance","validation","required-field","ruby"],"backgroundTag":"required-value-missing","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}