{"record":{"id":"dd408805e3984e8c","repo":"we-promise/sure","slug":"source-citation-does-not-match-grammar","errorCode":null,"errorMessage":"source citation does not match #{grammar}","messagePattern":"source citation does not match #(.+?)","errorType":"exception","errorClass":"Provenance::Citation::InvalidError","httpStatus":null,"severity":"error","filePath":"app/models/provenance/citation.rb","lineNumber":55,"sourceCode":"    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?\n        grade = match[:grade]\n\n        if estimated && grade.blank?\n          raise InvalidError, \"estimated values must carry a reliability grade, e.g. \\\"#{ESTIMATED_PREFIX}... (grade: C)\\\"\"\n        end\n\n        new(raw: value, text: text, estimated: estimated, grade: grade)\n      end\n\n      def valid?(raw)\n        parse!(raw)","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provenance/citation.rb#L37-L73","documentation":"Provenance::Citation.parse! raises InvalidError 'source citation does not match ...grammar' when the whole string fails the FORMAT regex: optional 'estimated: ' prefix, then citation text, then optional ' (grade: A|B|C)' suffix. Because earlier checks already handled blank, length, marker, and grade issues, hitting this usually means structural problems such as unbalanced parentheses that confuse the suffix match.","triggerScenarios":"Citation like 'Statement (unaudited' (unbalanced paren), 'Doc ((grade: A))', trailing ')', or a string whose only content is consumed leaving no text; also text consisting solely of a grade suffix.","commonSituations":"Parentheses in the document name interacting with the grade suffix; agent output with stray parens; markdown emphasis leaking in.","solutions":["Simplify to: document name, optionally ' (grade: X)' at the very end, e.g. 'Audited report 2025, p.12 (grade: B)'.","Remove or balance parentheses that are not the grade suffix.","Print Provenance::Citation.grammar in error output so the caller can self-correct: [\"estimated: \"] citation [\" (grade: A|B|C)\"].","Round-trip check: parse, then to_s, then parse again."],"exampleFix":"# before\nProvenance::Citation.parse!(\"Payroll summary (Q1 (grade: B)\")\n\n# after\nProvenance::Citation.parse!(\"Payroll summary Q1 (grade: B)\")","handlingStrategy":"try-catch","validationCode":"Provenance::Citation.valid?(value)","typeGuard":null,"tryCatchPattern":"begin\n  Provenance::Citation.parse!(source)\nrescue Provenance::Citation::InvalidError => e\n  # e.message embeds Provenance::Citation.grammar; surface it for self-correction\n  correct_and_retry(source, e.message)\nend","preventionTips":["Avoid stray parentheses in document names; reserve '(grade: X)' for the suffix.","Echo the grammar in agent prompts and validation messages.","Round-trip test: parse!(parsed.to_s) should succeed."],"tags":["provenance","validation","grammar","regex","ruby"],"backgroundTag":"source-citation-invalid","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}