we-promise/sure · error · Provenance::Citation::InvalidError
source citation must be #{MAX_LENGTH} characters or fewer
Error message
source citation must be #{MAX_LENGTH} characters or fewer What it means
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.
Source
Thrown at app/models/provenance/citation.rb:44
# 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
estimated = match[:estimated].present?View on GitHub (pinned to e69894adb9)
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.
Example fix
# before Provenance::Citation.parse!(agent_output[:source]) # after source = agent_output[:source].to_s.strip raise Provenance::Citation::InvalidError, "too long" if source.length > Provenance::Citation::MAX_LENGTH Provenance::Citation.parse!(source)
Defensive patterns
Strategy: validation
Validate before calling
value.to_s.strip.length <= Provenance::Citation::MAX_LENGTH
Type guard
def citation_length_ok?(value) = value.to_s.strip.length <= Provenance::Citation::MAX_LENGTH
Try / catch
begin Provenance::Citation.parse!(source) rescue Provenance::Citation::InvalidError => e errors.add(:source, e.message) end
Prevention
- 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.
When it happens
Trigger: 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.
Common situations: LLM prompt returning verbose provenance; UI textarea without maxlength; chaining several document names with delimiters until over the cap.
Related errors
- source citation is required
- estimated values must start with the exact prefix #{ESTIMATE
- reliability grade must be one of #{GRADES.join(", ")} (got #
- 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/3c6a4ca45765476c.
Report an issue: GitHub.