docs: def is_ill_formed(self):
          """Stronger set of checks than is_invalid for Refpkg.
  
          Checks that FASTA, Stockholm, JSON, and CSV files under known
          keys are all valid as well as calling is_invalid.  Returns
          either False or a string describing the error.
          """
          m = self.is_invalid()
          if m:
              return m
  
          required_keys = ('aln_fasta', 'aln_sto', 'seq_info', 'tree',
                           'taxonomy', 'phylo_model')
          for k in required_keys:
              if k not in self.contents['files']:
                  return "RefPkg has no key " + k
  
          # aln_fasta, seq_info, tree, and aln_sto must be valid FASTA,
          # CSV, Newick, and Stockholm files, respectively, and describe
          # the same sequences.
  
          with self.open_resource('aln_fasta') as f:
              firstline = f.readline()
              if firstline.startswith('>'):
                  f.seek(0)
              else:
                  return 'aln_fasta file is not valid FASTA.'
              fasta_names = {seq.id for seq in fastalite(f)}
  
          with self.open_resource('seq_info') as f:
              lines = list(csv.reader(f))
              headers = set(lines[0])
  
              # Check required headers
              for req_header in 'seqname', 'tax_id':
                  if req_header not in headers:
                      return "seq_info is missing {0}".format(req_header)
              lengths = {len(line) for line in lines}
              if len(lengths) > 1:
                  return "some lines in seq_info differ in field cout"
              csv_names = {line[0] for line in lines[1:]}
  
          with self.open_resource('aln_sto') as f:
              try:
                  sto_names = set(utils.parse_stockholm(f))
              except ValueError:
                  return 'aln_sto file is not valid Stockholm.'
  
          try:
              tree = dendropy.Tree.get(
                  path=self.resource_path('tree'),
                  schema='newick',
                  case_sensitive_taxon_labels=True,
                  preserve_underscores=True)
              tree_names = set(tree.taxon_namespace.labels())
          except Exception:
              return 'tree file is not valid Newick.'
  
          d = fasta_names.symmetric_difference(sto_names)
          if len(d) != 0:
              return "Names in aln_fasta did not match aln_sto.  Mismatches: " + \
                  ', '.join([str(x) for x in d])
          d = fasta_names.symmetric_difference(csv_names)
          if len(d) != 0:
              return "Names in aln_fasta did not match seq_info.  Mismatches: " + \
                  ', '.join([str(x) for x in d])
          d = fasta_names.symmetric_difference(tree_names)
          if len(d) != 0:
              return "Names in aln_fasta did not match nodes in tree.  Mismatches: " + \
                  ', '.join([str(x) for x in d])
  
          # Next make sure that taxonomy is valid CSV, phylo_model is valid JSON
          with self.open_resource('taxonomy') as f:
              lines = list(csv.reader(f))
              lengths = {len(line) for line in lines}
              if len(lengths) > 1:
                  return ("Taxonomy is invalid: not all lines had "
                          "the same number of fields.")
              # I don't try to check if the taxids match up to those
              # mentioned in aln_fasta, since that would make taxtastic
              # depend on RefsetInternalFasta in romperroom.
          with self.open_resource('phylo_model') as f:
              try:
                  json.load(f)
              except ValueError:
                  return "phylo_model is not valid JSON."
  
          return False