///|
/// Audit structural invariants that can be checked without network access.
pub fn Record::validate(self : Record) -> Array[ValidationIssue] {
  let issues : Array[ValidationIssue] = []
  let stats = self.sequence_stats()
  if self.locus.length != stats.length {
    issues.push({
      code: "sequence_length_mismatch",
      message: "LOCUS length does not match ORIGIN sequence length",
      severity: ValidationSeverity::Error,
    })
  }
  if self.features_by_key("source").is_empty() {
    issues.push({
      code: "missing_source_feature",
      message: "record has no source feature",
      severity: ValidationSeverity::Warning,
    })
  }
  for feature in self.features {
    for segment in feature.location.segments {
      match segment.remote {
        Some(accession) =>
          issues.push({
            code: "remote_location",
            message: "location references remote accession: " + accession,
            severity: ValidationSeverity::Warning,
          })
        None =>
          if segment.range.end > stats.length {
            issues.push({
              code: "feature_out_of_bounds",
              message: "feature location extends beyond ORIGIN",
              severity: ValidationSeverity::Error,
            })
          }
      }
      if segment.range.between {
        issues.push({
          code: "between_base_location",
          message: "feature uses a between-base location",
          severity: ValidationSeverity::Warning,
        })
      }
    }
  }
  for check in self.check_cds_translations() {
    if !check.ok {
      issues.push({
        code: "cds_translation_mismatch",
        message: "CDS translation does not match the ORIGIN sequence",
        severity: ValidationSeverity::Error,
      })
    }
  }
  issues
}

///|
/// True when an audit finding is an error rather than an advisory warning.
pub fn ValidationIssue::is_error(self : ValidationIssue) -> Bool {
  self.severity is ValidationSeverity::Error
}

///|
/// Render an audit finding for CLI and JSON consumers.
pub fn ValidationIssue::to_json_string(self : ValidationIssue) -> String {
  let severity = match self.severity {
    ValidationSeverity::Error => "error"
    ValidationSeverity::Warning => "warning"
  }
  "{\"code\":" +
  json_string(self.code) +
  ",\"message\":" +
  json_string(self.message) +
  ",\"severity\":" +
  json_string(severity) +
  "}"
}