we-promise/sure · error · Provenance::Citation::InvalidError
reliability grade must be one of #{GRADES.join(", ")} (got #
Error message
reliability grade must be one of #{GRADES.join(", ")} (got #{suffix[:grade].inspect}) What it means
Provenance::Citation.parse! raises InvalidError when the string ends with a '(grade: X)' suffix (matched by GRADE_SUFFIX) whose grade is not one of A, B, C. The suffix is matched separately from the main FORMAT regex so an invalid grade like '(grade: D)' fails loudly instead of folding into the citation text and silently producing an ungraded source.
Source
Thrown at app/models/provenance/citation.rb:51
class InvalidError < StandardError; end
attr_reader :raw, :text, :grade
class << self
# Returns a Citation, or raises InvalidError with a message written for the
# caller that has to fix it (an agent, usually).
def parse!(raw)
value = raw.to_s.strip
raise InvalidError, "source citation is required" if value.blank?
raise InvalidError, "source citation must be #{MAX_LENGTH} characters or fewer" if value.length > MAX_LENGTH
if value.match?(ESTIMATED_MARKER) && !value.start_with?(ESTIMATED_PREFIX)
raise InvalidError, "estimated values must start with the exact prefix #{ESTIMATED_PREFIX.inspect}"
end
if (suffix = value.match(GRADE_SUFFIX)) && !GRADES.include?(suffix[:grade])
raise InvalidError, "reliability grade must be one of #{GRADES.join(", ")} (got #{suffix[:grade].inspect})"
end
match = value.match(FORMAT)
raise InvalidError, "source citation does not match #{grammar}" unless match
text = match[:text].to_s.strip
if text.length < MIN_TEXT_LENGTH
raise InvalidError, "source citation must name the document it came from"
end
estimated = match[:estimated].present?
grade = match[:grade]
if estimated && grade.blank?
raise InvalidError, "estimated values must carry a reliability grade, e.g. \"#{ESTIMATED_PREFIX}... (grade: C)\""
end
new(raw: value, text: text, estimated: estimated, grade: grade)View on GitHub (pinned to e69894adb9)
Solutions
- Change the grade to A, B, or C (uppercase) — e.g. 'estimated: proxy from peer ETF (grade: C)'.
- If the grade is uncertain, C is the designated 'proxy/assumption' grade.
- Strip the suffix entirely if the value is not graded — but estimates then need a valid grade, so keep A-C for estimated:.
- Validate with Provenance::Citation.valid? before persisting and surface the allowed set in the UI.
Example fix
# before
Provenance::Citation.parse!("Bank statement p.3 (grade: D)")
# after
Provenance::Citation.parse!("Bank statement p.3 (grade: C)") Defensive patterns
Strategy: validation
Validate before calling
suffix = value.to_s.match(/\(grade:\s*([^)]*)\)\s*\z/) suffix.nil? || %w[A B C].include?(suffix[1])
Type guard
def grade_suffix_ok?(v) m = v.to_s.match(/\(grade:\s*([^)]*)\)\s*\z/) m.nil? || Provenance::Citation::GRADES.include?(m[1]) end
Try / catch
begin Provenance::Citation.parse!(source) rescue Provenance::Citation::InvalidError => e errors.add(:source, e.message) # message names allowed grades and the bad value end
Prevention
- Constrain grade input to a select of A/B/C in the UI.
- Tell agents only A, B, C exist; C is for proxies/assumptions.
- Never let the suffix fail silently into the citation text — keep GRADE_SUFFIX and FORMAT in sync when editing.
When it happens
Trigger: Citations ending in '(grade: D)', '(grade: a)' (lowercase, not in the A-C set as matched — FORMAT only captures [ABC] uppercase), '(grade: A+)', or '(grade: )' with an empty value.
Common situations: Agent inventing a wider grade scale; user typing lowercase or numeric grades; typo in the suffix.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- source citation is required
- source citation must be #{MAX_LENGTH} characters or fewer
- estimated values must start with the exact prefix #{ESTIMATE
- source citation does not match #{grammar}
- source citation must name the document it came from
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/2f332110e1e7a103.
Report an issue: GitHub.