{"record":{"id":"3c6a4ca45765476c","repo":"we-promise/sure","slug":"source-citation-must-be-max-length-characters-o","errorCode":null,"errorMessage":"source citation must be #{MAX_LENGTH} characters or fewer","messagePattern":"source citation must be #(.+?) characters or fewer","errorType":"exception","errorClass":"Provenance::Citation::InvalidError","httpStatus":null,"severity":"error","filePath":"app/models/provenance/citation.rb","lineNumber":44,"sourceCode":"    # 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\n        estimated = match[:estimated].present?","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provenance/citation.rb#L26-L62","documentation":"Provenance::Citation.parse! raises InvalidError when the stripped citation exceeds MAX_LENGTH (500) characters. The cap keeps citations usable for display and storage; agent-generated prose citations (long summaries, pasted excerpts) are the usual offender.","triggerScenarios":"Citation.parse! on a string longer than 500 chars after strip; an agent pasting a paragraph or full document excerpt as the source; concatenating multiple sources into one string.","commonSituations":"LLM prompt returning verbose provenance; UI textarea without maxlength; chaining several document names with delimiters until over the cap.","solutions":["Shorten the citation to a document name plus locator (e.g. 'Statement 2026-03 p.4, line 12') under 500 chars.","Add a client-side/UI length check (maxlength=500) and a model validation before parse!.","If multiple sources matter, cite the primary one; the grammar holds one citation per value.","Truncate deterministically and re-run Provenance::Citation.valid? to confirm it still parses."],"exampleFix":"# before\nProvenance::Citation.parse!(agent_output[:source])\n\n# after\nsource = agent_output[:source].to_s.strip\nraise Provenance::Citation::InvalidError, \"too long\" if source.length > Provenance::Citation::MAX_LENGTH\nProvenance::Citation.parse!(source)","handlingStrategy":"validation","validationCode":"value.to_s.strip.length <= Provenance::Citation::MAX_LENGTH","typeGuard":"def citation_length_ok?(value) = value.to_s.strip.length <= Provenance::Citation::MAX_LENGTH","tryCatchPattern":"begin\n  Provenance::Citation.parse!(source)\nrescue Provenance::Citation::InvalidError => e\n  errors.add(:source, e.message)\nend","preventionTips":["Set maxlength=500 on citation inputs.","Prompt agents for a document name plus locator, not a prose excerpt.","Check length before parse so users get a character count, not a raised error."],"tags":["provenance","validation","length-limit","ruby"],"backgroundTag":"max-length-exceeded","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}