we-promise/sure · error · Provenance::Citation::InvalidError

source citation is required

Error message

source citation is required

What it means

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.

Source

Thrown at app/models/provenance/citation.rb:43

    # match here, folding the grade into the citation text and yielding an
    # ungraded source — silently losing the reliability the caller supplied.
    FORMAT = /\A(?<estimated>estimated:\s)?(?<text>.+?)(?:\s?\(grade:\s*(?<grade>[ABC])\))?\z/
    # Matched separately so "(grade: D)" fails loudly instead of folding into the
    # citation text and passing as an ungraded — but plausible-looking — source.
    GRADE_SUFFIX = /\(grade:\s*(?<grade>[^)]*)\)\s*\z/
    ESTIMATED_MARKER = /\Aestimated\s*:/i

    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

View on GitHub (pinned to e69894adb9)

Solutions

  1. Supply a citation naming the document, e.g. 'Private bank statement 2026-03-31, line 12'.
  2. If the value is an estimate, use 'estimated: ... (grade: C)' format.
  3. Use Provenance::Citation.valid?(value) to check before the write and reject with a user-facing validation message.
  4. Make the citation field required upstream (model validation or form required attribute).

Example fix

# before
Provenance::Citation.parse!(params[:source])

# after
raise ArgumentError, "source is required" if params[:source].to_s.strip.empty?
Provenance::Citation.parse!(params[:source])
Defensive patterns

Strategy: validation

Validate before calling

Provenance::Citation.valid?(value) # false when blank; check before writing

Type guard

def valid_citation?(value) = !value.to_s.strip.empty? && Provenance::Citation.valid?(value)

Try / catch

begin
  Provenance::Citation.parse!(source)
rescue Provenance::Citation::InvalidError => e
  errors.add(:source, e.message) # feed message back to the agent/user
end

Prevention

When it happens

Trigger: 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.

Common situations: Agent pipeline where the model returned no source; optional form field treated as skippable; default nil passed through a data-import path.

Related errors


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