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

  1. Change the grade to A, B, or C (uppercase) — e.g. 'estimated: proxy from peer ETF (grade: C)'.
  2. If the grade is uncertain, C is the designated 'proxy/assumption' grade.
  3. Strip the suffix entirely if the value is not graded — but estimates then need a valid grade, so keep A-C for estimated:.
  4. 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

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


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/2f332110e1e7a103. Report an issue: GitHub.