{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Config",
  "type": "object",
  "properties": {
    "input": {
      "description": "The input configuration",
      "$ref": "#/$defs/Input"
    },
    "output": {
      "anyOf": [
        {
          "$ref": "#/$defs/Output"
        },
        {
          "type": "null"
        }
      ]
    },
    "barcodes": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": {
        "$ref": "#/$defs/Barcodes"
      }
    },
    "options": {
      "anyOf": [
        {
          "$ref": "#/$defs/Options"
        },
        {
          "type": "null"
        }
      ]
    },
    "transform": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Transformation"
      }
    },
    "benchmark": {
      "anyOf": [
        {
          "$ref": "#/$defs/Benchmark"
        },
        {
          "type": "null"
        }
      ]
    },
    "step": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Transformation"
      }
    },
    "steps": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Transformation"
      }
    },
    "transforms": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Transformation"
      }
    }
  },
  "required": [
    "input"
  ],
  "$defs": {
    "Input": {
      "description": "Input configuration",
      "type": "object",
      "properties": {
        "interleaved": {
          "description": "whether you have input files with interleaved reads, or one file per segment\nIf interleaved, define the name of the segments here.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "options": {
          "$ref": "#/$defs/InputOptions"
        }
      },
      "additionalProperties": {
        "$ref": "#/$defs/StringOrVecString"
      }
    },
    "StringOrVecString": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      ]
    },
    "InputOptions": {
      "type": "object",
      "properties": {
        "fasta_fake_quality": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "bam_include_mapped": {
          "type": [
            "boolean",
            "null"
          ]
        },
        "bam_include_unmapped": {
          "type": [
            "boolean",
            "null"
          ]
        },
        "read_comment_character": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "threads_per_segment": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "read_comment_char": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        }
      },
      "required": [
        "read_comment_character"
      ]
    },
    "Output": {
      "type": "object",
      "properties": {
        "prefix": {
          "type": "string"
        },
        "ix_separator": {
          "type": "string",
          "default": ""
        },
        "compression_threads": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        }
      },
      "required": [
        "prefix"
      ]
    },
    "Barcodes": {
      "type": "object",
      "properties": {
        "from_file": {
          "anyOf": [
            {
              "$ref": "#/$defs/BarcodesFromFile"
            },
            {
              "type": "null"
            }
          ]
        },
        "barcode_to_name": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "required": [
        "barcode_to_name"
      ]
    },
    "BarcodesFromFile": {
      "type": "object",
      "properties": {
        "filename": {
          "type": "string"
        },
        "read_comment_character": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "read_comment_char": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        }
      },
      "required": [
        "filename"
      ]
    },
    "Options": {
      "type": "object",
      "properties": {
        "threads": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "max_molecules_in_flight": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "block_size": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "buffer_size": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "output_buffer_size": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "accept_duplicate_files": {
          "type": [
            "boolean",
            "null"
          ]
        },
        "spot_check_read_pairing": {
          "type": [
            "boolean",
            "null"
          ]
        },
        "thread_count": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "max_reads_in_flight": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        }
      }
    },
    "Transformation": {
      "oneOf": [
        {
          "description": "Cut a fixed number of bases from the start of reads",
          "type": "object",
          "properties": {
            "n": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "segment": {
              "type": "string"
            },
            "if_tag": {
              "type": [
                "string",
                "null"
              ]
            },
            "action": {
              "type": "string",
              "const": "CutStart"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Cut a fixed number of bases from the end of reads",
          "type": "object",
          "properties": {
            "n": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "segment": {
              "type": "string"
            },
            "if_tag": {
              "type": [
                "string",
                "null"
              ]
            },
            "action": {
              "type": "string",
              "const": "CutEnd"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Truncate reads to a fixed length",
          "type": "object",
          "properties": {
            "n": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "segment": {
              "type": "string"
            },
            "if_tag": {
              "type": [
                "string",
                "null"
              ]
            },
            "action": {
              "type": "string",
              "const": "Truncate"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "add a fixed prefix to the start of reads",
          "type": "object",
          "properties": {
            "segment": {
              "type": "string"
            },
            "seq": {
              "type": "string"
            },
            "qual": {
              "type": "string"
            },
            "encoding": {
              "$ref": "#/$defs/PhredEncoding"
            },
            "if_tag": {
              "type": [
                "string",
                "null"
              ]
            },
            "action": {
              "type": "string",
              "const": "Prefix"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Add a fixed sequence to the end of reads",
          "type": "object",
          "properties": {
            "segment": {
              "type": "string"
            },
            "seq": {
              "type": "string"
            },
            "qual": {
              "type": "string"
            },
            "encoding": {
              "$ref": "#/$defs/PhredEncoding"
            },
            "if_tag": {
              "type": [
                "string",
                "null"
              ]
            },
            "action": {
              "type": "string",
              "const": "Postfix"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Convert PHRED scores between encodings",
          "type": "object",
          "properties": {
            "from": {
              "$ref": "#/$defs/PhredEncoding"
            },
            "to": {
              "$ref": "#/$defs/PhredEncoding"
            },
            "action": {
              "type": "string",
              "const": "ConvertQuality"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Reverse complement a read",
          "type": "object",
          "properties": {
            "target": {
              "type": "string"
            },
            "if_tag": {
              "type": [
                "string",
                "null"
              ]
            },
            "action": {
              "type": "string",
              "const": "ReverseComplement"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Rename (and/or renumber) reads by applying a regex",
          "type": "object",
          "properties": {
            "search": {
              "type": "string"
            },
            "replacement": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "Rename"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Swap two segments.\n\nWith `if_tag` set, the swap is per-read (only reads where the tag is truthy\nexchange their mates). A per-read swap moves a read to the *other* segment,\nso any location tag captured against either swapped segment is **forgotten**\n— a location column may alias only one segment, and a per-read swap would\nsplit it across two. To carry such data across a conditional swap, snapshot\nit to a plain String with `ConcatTags` beforehand. An unconditional swap\nkeeps location tags: their `source_id` is simply re-stamped to the segment\nthat now holds their bytes.",
          "type": "object",
          "properties": {
            "if_tag": {
              "type": [
                "string",
                "null"
              ]
            },
            "segment_a": {
              "type": "string"
            },
            "segment_b": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "Swap"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Convert a read, name, tag into lower case",
          "type": "object",
          "properties": {
            "target": {
              "type": "string"
            },
            "if_tag": {
              "type": [
                "string",
                "null"
              ]
            },
            "action": {
              "type": "string",
              "const": "Lowercase"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Convert a read, name, tag into upper case",
          "type": "object",
          "properties": {
            "target": {
              "type": "string"
            },
            "if_tag": {
              "type": [
                "string",
                "null"
              ]
            },
            "action": {
              "type": "string",
              "const": "Uppercase"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Trim reads at a tag's position",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "direction": {
              "$ref": "#/$defs/Direction"
            },
            "keep_tag": {
              "type": "boolean"
            },
            "action": {
              "type": "string",
              "const": "TrimAtTag"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Merge paired end reads if they're overlapping",
          "type": "object",
          "properties": {
            "algorithm": {
              "description": "Algorithm to use for overlap scoring and mismatch resolution",
              "$ref": "#/$defs/Algorithm"
            },
            "min_overlap": {
              "description": "Minimum overlap length required for merging (suggested: 30, minimum: 5)",
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "max_mismatch_rate": {
              "description": "Maximum allowed mismatch rate (0.0 to 1.0, suggested: 0.2)",
              "type": "number",
              "format": "double"
            },
            "max_mismatch_count": {
              "description": "Maximum allowed absolute number of mismatches (suggested: 5)",
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "no_overlap_strategy": {
              "description": "Strategy when no overlap is found",
              "$ref": "#/$defs/NoOverlapStrategy"
            },
            "out_label": {
              "description": "Tag label to store merge status (suggested: \"merged\")\nTag will be true if reads were merged, false otherwise",
              "type": [
                "string",
                "null"
              ]
            },
            "concatenate_spacer": {
              "description": "Spacer sequence to use when concatenating (required if `no_overlap_strategy` = 'concatenate')",
              "type": [
                "string",
                "null"
              ]
            },
            "spacer_quality_char": {
              "description": "Quality score to use for spacer bases (suggested: 33, which is Phred quality 0)",
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "reverse_complement_segment2": {
              "description": "Whether to reverse complement segment2 before merging",
              "type": "boolean"
            },
            "segment1": {
              "type": "string"
            },
            "segment2": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "MergeReads"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "type": "object",
          "properties": {
            "source": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "accepted": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "action": {
              "type": "string",
              "enum": [
                "Matches",
                "TagMatches",
                "matches",
                "tagmatches"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Filter reads by presence/value of a (non-numeric) tag",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "keep_or_remove": {
              "$ref": "#/$defs/KeepOrRemove"
            },
            "action": {
              "type": "string",
              "const": "FilterByTag"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Filter reads by threshold on a (numeric) tag",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "min_value": {
              "type": [
                "number",
                "null"
              ],
              "format": "double"
            },
            "max_value": {
              "type": [
                "number",
                "null"
              ],
              "format": "double"
            },
            "keep_or_remove": {
              "$ref": "#/$defs/KeepOrRemove"
            },
            "action": {
              "type": "string",
              "const": "FilterByNumericTag"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Filter all reads after the first n",
          "type": "object",
          "properties": {
            "n": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "const": "Head"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Skip the first n reads",
          "type": "object",
          "properties": {
            "n": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "const": "Skip"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Filter empty reads (without sequence, length == 0)",
          "type": "object",
          "properties": {
            "segment": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "FilterEmpty"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Sample reads by probability. Cheap.",
          "type": "object",
          "properties": {
            "p": {
              "type": "number",
              "format": "double"
            },
            "seed": {
              "type": "integer",
              "format": "uint64",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "enum": [
                "FilterSample",
                "sample",
                "filtersample"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Fairly sample reads (expensive!)",
          "type": "object",
          "properties": {
            "n": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "seed": {
              "type": "integer",
              "format": "uint64",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "enum": [
                "FilterReservoirSample",
                "reservoirsample",
                "filterreservoirsample"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Spot check the read names matching across segments",
          "type": "object",
          "properties": {
            "sample_stride": {
              "type": "integer",
              "format": "uint64",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "enum": [
                "ValidateReadPairing",
                "SpotCheckReadNames",
                "validatereadpairing",
                "spotcheckreadnames"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Validate that the sequence is only consisting of the specified bases",
          "type": "object",
          "properties": {
            "allowed": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "enum": [
                "ValidateSeq",
                "ValidateSequence",
                "validateseq",
                "validatesequence"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Validate that quality scores are within Sanger (PHRED 33) range.",
          "type": "object",
          "properties": {
            "encoding": {
              "$ref": "#/$defs/PhredEncoding"
            },
            "segment": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "enum": [
                "ValidateQuality",
                "ValidateQual",
                "validatequality",
                "validatequal"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Validate that read names between segments match",
          "type": "object",
          "properties": {
            "readname_end_char": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "sample_stride": {
              "type": "integer",
              "format": "uint64",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "const": "ValidateName"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Verify that all reads have the same length",
          "type": "object",
          "properties": {
            "source": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "ValidateAllReadsSameLength"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Validate that read names conform to the SAM/BAM specification.\n\nValid read names match `[!-?A-~]{1,254}` — printable ASCII excluding `@` and\nspace, with a maximum length of 254 characters.",
          "type": "object",
          "properties": {
            "action": {
              "type": "string",
              "const": "ValidateReadNamesPrintable"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Extract a IUPAC described sequence from the read. E.g. an adapter.\nCan be at the start (anchor = Left, the end (anchor = Right),\nor anywhere (anchor = Anywhere) within the read.\nThe search parameter can be either a single IUPAC string or a list of IUPAC strings.\nIf multiple strings are provided, the first hit wins.",
          "type": "object",
          "properties": {
            "search": {
              "$ref": "#/$defs/StringOrVecString"
            },
            "segment": {
              "type": "string"
            },
            "anchor": {
              "$ref": "#/$defs/Anchor"
            },
            "max_anchor_distance": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "out_label": {
              "type": "string"
            },
            "max_mismatches": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "on_tie": {
              "$ref": "#/$defs/TieBreak"
            },
            "action": {
              "type": "string",
              "const": "ExtractIUPAC"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Extract an IUPAC-described sequence while tolerating insertions and deletions.\nUseful for adapters where small indels are expected.",
          "type": "object",
          "properties": {
            "search": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "anchor": {
              "$ref": "#/$defs/Anchor"
            },
            "out_label": {
              "type": "string"
            },
            "max_mismatches": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "max_indel_bases": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "max_total_edits": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "const": "ExtractIUPACWithIndel"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Region by regular expression",
          "type": "object",
          "properties": {
            "search": {
              "type": "string"
            },
            "replacement": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "source": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "ExtractRegex"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Define a region by coordinates",
          "type": "object",
          "properties": {
            "start": {
              "type": "integer",
              "format": "int"
            },
            "len": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "source": {
              "description": "Source for extraction - segment name, \"tag:name\" for tag source, or \"name:segment\" for read name source",
              "type": "string"
            },
            "anchor": {
              "description": "Is the region from the `Start` or the `End` of the source?",
              "$ref": "#/$defs/RegionAnchor"
            },
            "out_label": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "ExtractRegion"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Extract regions by coordinates\nthat is by (segment|source, 0-based start, length)\ndefined triplets, joined with (possibly empty) separator.",
          "type": "object",
          "properties": {
            "regions": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/RegionDefinition"
              }
            },
            "source": {
              "description": "Source for extraction - segment name, \"tag:name\" for tag source, or \"name:segment\" for read name source",
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "ExtractRegions"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Convert read length into a tag",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "CalcLength"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Quantify base occurrence rate or count",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "relative": {
              "type": "boolean"
            },
            "bases_to_count": {
              "type": "string"
            },
            "bases_to_ignore": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "CalcBaseContent"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Calculate (relative) GC content. See `CalcBaseContent`",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "relative": {
              "type": "boolean"
            },
            "action": {
              "type": "string",
              "enum": [
                "CalcGCContent",
                "CalcGCCount",
                "calcgccontent",
                "calcgccount"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Count the number of N. See `CalcBaseContent` for general case",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "relative": {
              "type": "boolean"
            },
            "action": {
              "type": "string",
              "enum": [
                "CalcNContent",
                "CalcNCount",
                "calcncontent",
                "calcncount"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Calculate complexity score (# transitions / (len -1))",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "CalcComplexity"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Calculate bases passing quality threshold (in any direction)",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "threshold": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "operator": {
              "$ref": "#/$defs/Operator"
            },
            "relative": {
              "type": "boolean"
            },
            "segment": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "CalcQualifiedBases"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Calculate expected error from (sanger, 33 based) PHRED",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "aggregate": {
              "$ref": "#/$defs/ExpectedErrorAggregate"
            },
            "action": {
              "type": "string",
              "const": "CalcExpectedError"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Calculate minimum quality byte value across a segment or a tagged region",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "source": {
              "type": "string"
            },
            "offset": {
              "type": "integer",
              "format": "int8",
              "minimum": -128,
              "maximum": 127
            },
            "action": {
              "type": "string",
              "const": "CalcWorstQuality"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Quantify Kmer occurance vs database",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "filename": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "k": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "count_reverse_complement": {
              "type": "boolean"
            },
            "min_count": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "const": "CalcKmers"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Divide a numeric tag value by the read length to produce a rate (0..=1)",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "segment": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "ConvertToRate"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Convert region tag to a length tag",
          "type": "object",
          "properties": {
            "out_label": {
              "type": "string"
            },
            "in_label": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "ConvertRegionsToLength"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Evaluate an equation on tags",
          "type": "object",
          "properties": {
            "out_label": {
              "description": "The tag label to store the result",
              "type": "string"
            },
            "expression": {
              "description": "The arithmetic or boolean expression to evaluate.\nVariables in the expression should match existing tag names.\nExpressions using comparison or logical operators (`<`, `>`, `==`, `!=`, `&&`, `||`, `!`)\nproduce a bool tag; pure arithmetic expressions produce a numeric tag.",
              "type": "string"
            },
            "result_type": {
              "anyOf": [
                {
                  "$ref": "#/$defs/ResultType"
                },
                {
                  "type": "null"
                }
              ]
            },
            "action": {
              "type": "string",
              "enum": [
                "EvalExpression",
                "EvaluateExpression",
                "evalexpression",
                "evaluateexpression"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Compare two string- or location-valued tags lexicographically (byte-by-byte).\n\nReturns -1.0 if `tag_a` < `tag_b`, 0.0 if equal, 1.0 if `tag_a` > `tag_b`,\nlike Rust's `PartialOrd` on byte slices. Missing on either input → Missing output.\nRaises a runtime error if the two byte sequences for a read have different lengths.",
          "type": "object",
          "properties": {
            "in_label_a": {
              "type": "string"
            },
            "in_label_b": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "CompareStringTags"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Extract regions of low quality (configurable)",
          "type": "object",
          "properties": {
            "segment": {
              "type": "string"
            },
            "min_quality": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "min_length": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "out_label": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "ExtractRegionsOfLowQuality"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Find the longest polyX",
          "type": "object",
          "properties": {
            "segment": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "min_length": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "base": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "max_mismatch_rate": {
              "type": "number",
              "format": "double"
            },
            "max_consecutive_mismatches": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "const": "ExtractLongestPolyX"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Extract ends that are homo-polymers into a tag",
          "type": "object",
          "properties": {
            "segment": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "min_length": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "base": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "max_mismatch_rate": {
              "type": "number",
              "format": "double"
            },
            "max_consecutive_mismatches": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "fastp_mode": {
              "description": "Use fastp's polyG trimming algorithm (requires base = 'G').\nApplies fastp's fixed constants: 1 mismatch allowed per 8 bases, max 5 total mismatches.",
              "type": [
                "boolean",
                "null"
              ]
            },
            "action": {
              "type": "string",
              "const": "ExtractPolyTail"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Extract a IUPAC sequence (or a prefix of it) at the end of a read into a tag.",
          "type": "object",
          "properties": {
            "segment": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "min_length": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "max_mismatches": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "search": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "ExtractIUPACSuffix"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Turn low quality start's of reads into a tag",
          "type": "object",
          "properties": {
            "segment": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "min_qual": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "action": {
              "type": "string",
              "const": "ExtractLowQualityStart"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Turn low quality end's of reads into a tag",
          "type": "object",
          "properties": {
            "segment": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "min_qual": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "action": {
              "type": "string",
              "const": "ExtractLowQualityEnd"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Tag duplicate reads",
          "type": "object",
          "properties": {
            "source": {
              "type": "string"
            },
            "out_label": {
              "type": "string"
            },
            "false_positive_rate": {
              "type": "number",
              "format": "double"
            },
            "seed": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint64",
              "minimum": 0
            },
            "initial_filter_capacity": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint",
              "minimum": 0
            },
            "action": {
              "type": "string",
              "const": "TagDuplicates"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Tag whether reads are in another file (by sequence)",
          "type": "object",
          "properties": {
            "filename": {
              "type": "string"
            },
            "source": {
              "type": "string"
            },
            "other_readname_end_character": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "out_label": {
              "type": "string"
            },
            "seed": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint64",
              "minimum": 0
            },
            "false_positive_rate": {
              "type": "number",
              "format": "double"
            },
            "include_mapped": {
              "type": "boolean"
            },
            "include_unmapped": {
              "type": "boolean"
            },
            "action": {
              "type": "string",
              "const": "TagOtherFile"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Store the tag's 'sequence', probably modified by a previous step,\nback into the reads' sequence.\n\nThis is exactly [`StoreTagInSequence`] with the tag used as both the content\nsource and the position (anchor `Replace`): the tag's (possibly modified) bytes\noverwrite the location they came from. An unmodified location tag is already the\nread's own bytes, so writing it back is a no-op; a tag whose content diverged\n(a regex replacement, a reverse-complement or case change) replaces those bytes.\nIts quality travels with it.\n\nIf the location is a single span the content may be longer or shorter than what\nit replaces (the read grows or shrinks); if it covers several disjoint regions\nthe content must match their total length. The location is lifted through any\nedits applied since it was captured, so the content lands where the tag *now*\nsits in the read. If an intervening edit cut that location away, `on_lost`\ndecides what happens (default: leave the read unchanged).\n\n[`StoreTagInSequence`]: super::store_tag_in_sequence::StoreTagInSequence",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "on_lost": {
              "description": "What to do when the tag's location was lost to a later edit.",
              "$ref": "#/$defs/OnLost"
            },
            "action": {
              "type": "string",
              "const": "StoreTagBackInSequence"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Insert a tag's string value into a read sequence at the position defined by another\nlocation tag.\n\n`in_value_label` must be a location or string tag — its byte sequence (via `as_ref()` on\nthe value) is the text inserted into the read.\n\nA location value carries its own (possibly edited) quality with it; a string value has\nno quality, so its inserted bases get `~` (Phred 93, maximum Sanger quality).\n\n`in_position_label` must be a location tag — it defines *where* to act:\n\n- `anchor = \"Start\"` (alias `\"left\"`) — inserts **before** the leftmost start of the\n  location (the hit with the smallest `start` coordinate).\n- `anchor = \"End\"` (alias `\"right\"`) — inserts **after** the rightmost end of the\n  location (the hit with the largest `start + len` value).\n- `anchor = \"Replace\"` — overwrites the location. A single span (or several regions that\n  lift to a contiguous range) may be replaced by content of any length; several disjoint\n  regions require the content to match their total length, and the content is then laid\n  down byte-for-byte over exactly the covered positions. Any other disjoint case is a\n  runtime error.\n\nAll coordinates are lifted through edits applied since the position tag was captured, and\nthe length change is recorded so later location tags shift correctly.\n\nIf either tag value is `Missing`, the position tag carries no location, or an edit cut the\nposition away (subject to `on_lost`), the read is left unchanged.",
          "type": "object",
          "properties": {
            "in_value_label": {
              "description": "Location or string tag whose sequence to insert into the read.",
              "type": "string"
            },
            "in_position_label": {
              "description": "Location tag that defines the insertion position.",
              "type": "string"
            },
            "anchor": {
              "description": "Where to insert relative to the position tag.",
              "$ref": "#/$defs/ReplacementAnchor"
            },
            "on_lost": {
              "description": "What to do when the position tag's location was lost to a later edit.",
              "$ref": "#/$defs/OnLost"
            },
            "action": {
              "type": "string",
              "const": "StoreTagInSequence"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Replace all bases with this (region) tag with one base",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "letter": {
              "description": "Provide the replacement letter as a single character (e.g., 'N') or its ASCII numeric value (e.g., 78 for 'N').",
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "action": {
              "type": "string",
              "const": "ReplaceTagWithLetter"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Concatenate multiple tags into a single tag.\n\nTakes n >= 2 tags (which can be location tags or string tags) and combines them:\n- If all tags are Location: appends regions and concatenates sequences\n- If all tags are String: concatenates strings with optional separator\n- If mixed (Location + String): converts all to strings and concatenates\n\n# Examples\n\n```toml\n[[step]]\naction = \"ConcatTags\"\nin_labels = [\"barcode1\", \"barcode2\"]\nout_label = \"combined_barcode\"\nseparator = \"_\"  # Optional, only used for string concatenation\n```",
          "type": "object",
          "properties": {
            "in_labels": {
              "description": "Input tag labels to concatenate (must have at least 2)",
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "out_label": {
              "description": "Output tag label for the concatenated result",
              "type": "string"
            },
            "separator": {
              "description": "Separator to use when concatenating strings (optional, defaults to empty)",
              "type": "string"
            },
            "on_missing": {
              "description": "Behavior when encountering missing tags\n- `merge_present`: Skip missing tags and merge only the present ones\n- `set_missing`: Set the output tag to missing if any input tag is missing",
              "$ref": "#/$defs/OnMissing"
            },
            "action": {
              "type": "string",
              "const": "ConcatTags"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Return the primary tag if it is not missing, otherwise return the secondary tag.\n\nBoth tags must have the same kind (Location or String), or be a\nLocation/String mixture — in that case the output is always a String tag\n(Location values are converted to their sequence representation).",
          "type": "object",
          "properties": {
            "in_label_primary": {
              "description": "Primary tag: used when not missing.",
              "type": "string"
            },
            "in_label_secondary": {
              "description": "Secondary tag: used when the primary tag is missing.",
              "type": "string"
            },
            "out_label": {
              "description": "Output tag label for the result.",
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "FillMissing"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Remove all tags from memory",
          "type": "object",
          "properties": {
            "action": {
              "type": "string",
              "const": "ForgetAllTags"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "remove one tag from memory",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "ForgetTag"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Store currently present tags as comments on read names.\nComments are key=value pairs, separated by `comment_separator`\nwhich defaults to '|'.\nThey get inserted at the first `comment_insert_char`,\nwhich defaults to space. The `comment_insert_char` basically moves\nto the right.\n\nThat means a read name like\n@ERR12828869.501 A00627:18:HGV7TDSXX:3:1101:10502:5274/1\nbecomes\n@ERR12828869.501|key=value|key2=value2 A00627:18:HGV7TDSXX:3:1101:10502:5274/1\n\nThis way, your added tags will survive STAR alignment.\n(STAR always cuts at the first space, and by default also on /)\n\n(If the `comment_insert_char` is not present, we simply add at the right)\n\n\nBe default, comments are only placed on Read1.\nIf you need them somewhere else, or an all reads, change the target (to \"All\")",
          "type": "object",
          "properties": {
            "in_labels": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "segment": {
              "type": "string"
            },
            "comment_separator": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "comment_insert_char": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "region_separator": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "enum": [
                "StoreTagInComment",
                "StoreTagsInComment",
                "storetagincomment",
                "storetagsincomment"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Store tag values into FASTQ files.\n\nFiles are named using the pattern: `{output_prefix}_{infix}.tag.fastq.{suffix}`\nWhen demultiplexing: `{output_prefix}_{infix}_{barcode}.tag.fastq.{suffix}`\n\nOptionally adds comment tags to read names before writing, similar to `StoreTagInComment`.",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "comment_tags": {
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string"
              }
            },
            "comment_separator": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "comment_insert_char": {
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "region_separator": {
              "type": "string"
            },
            "format": {
              "$ref": "#/$defs/FileFormat"
            },
            "compression": {
              "$ref": "#/$defs/CompressionFormat"
            },
            "compression_level": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "action": {
              "type": "string",
              "enum": [
                "StoreTagInFastQ",
                "StoreTagsInFastQ",
                "storetaginfastq",
                "storetagsinfastq"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Store all currently defined tags in a TSV",
          "type": "object",
          "properties": {
            "infix": {
              "type": [
                "string",
                "null"
              ]
            },
            "compression": {
              "$ref": "#/$defs/CompressionFormat"
            },
            "compression_level": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "region_separator": {
              "type": "string"
            },
            "include_read_name": {
              "type": "boolean"
            },
            "include_read_comment": {
              "type": "boolean"
            },
            "in_labels": {
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string"
              }
            },
            "action": {
              "type": "string",
              "enum": [
                "StoreTagsInTable",
                "StoreTagInTable",
                "OutputTable",
                "OutputTSV",
                "storetagsintable",
                "storetagintable",
                "outputtable",
                "outputtsv"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Collect (`gene_idx`, `cell_idx`, `umi_2bit`) triples per read, then write binary\ndata file(s) and lookup tables on finalize.\n\nBoth cell and gene barcodes are looked up by sequence in [barcodes.*] tables.\nIndex 0 is reserved for \"unmatched\"; real barcodes are 1-indexed.\nOutput: one binary file per demultiplex group + shared lookup .txt files.",
          "type": "object",
          "properties": {
            "cell_tag": {
              "description": "Tag carrying the cell-barcode sequence (Location or String)",
              "type": "string"
            },
            "gene_tag": {
              "description": "Tag carrying the gene-barcode sequence (Location or String)",
              "type": "string"
            },
            "umi_tag": {
              "description": "Tag carrying the raw UMI sequence (Location or String)",
              "type": "string"
            },
            "umi_aggregation": {
              "$ref": "#/$defs/UMIAggregation"
            },
            "cell_barcodes": {
              "description": "[barcode.*] sec listing all valid cell barcodes (sequence → name)",
              "type": "string"
            },
            "gene_barcodes": {
              "description": "[barcode.*] section listing all valid gene identifiers (sequence → name)",
              "type": "string"
            },
            "cell_tag_contains_barcode": {
              "description": "Whether `cell_tag` values are barcode sequences (true) or corrected labels (false).\nDefault: auto-detect — true for Location tags, false for String tags.",
              "type": [
                "boolean",
                "null"
              ]
            },
            "gene_tag_contains_barcode": {
              "description": "Whether `gene_tag` values are barcode sequences (true) or corrected labels (false).\nDefault: auto-detect — true for Location tags, false for String tags.",
              "type": [
                "boolean",
                "null"
              ]
            },
            "infix": {
              "description": "Infix for output filenames",
              "type": [
                "string",
                "null"
              ]
            },
            "compression": {
              "description": "Compression for the binary data file (lookup tables are always plain text)",
              "$ref": "#/$defs/CompressionFormat"
            },
            "compression_level": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "action": {
              "type": "string",
              "const": "StoreSingleCellMatrix"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Write a histogram of tag values to a JSON file.",
          "type": "object",
          "properties": {
            "infix": {
              "type": "string"
            },
            "in_label": {
              "type": "string"
            },
            "region_separator": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "QuantifyTag"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "output a progress indicator",
          "type": "object",
          "properties": {
            "n": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "output_infix": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "Progress"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Include a report at this position",
          "type": "object",
          "properties": {
            "name": {
              "type": "string"
            },
            "count": {
              "type": "boolean"
            },
            "base_statistics": {
              "type": "boolean"
            },
            "length_distribution": {
              "type": "boolean"
            },
            "duplicate_count_per_read": {
              "type": "boolean"
            },
            "duplicate_count_per_fragment": {
              "type": "boolean"
            },
            "count_oligos": {
              "type": [
                "object",
                "null"
              ],
              "additionalProperties": {
                "type": "string"
              }
            },
            "count_oligos_segment": {
              "type": "string"
            },
            "tag_histograms": {
              "description": "Generate histograms for specified tags",
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string"
              }
            },
            "action": {
              "type": "string",
              "const": "Report"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Inspect reads within the workflow",
          "type": "object",
          "properties": {
            "n": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "segment": {
              "type": "string"
            },
            "infix": {
              "type": "string"
            },
            "suffix": {
              "type": [
                "string",
                "null"
              ]
            },
            "format": {
              "$ref": "#/$defs/FileFormat"
            },
            "compression": {
              "$ref": "#/$defs/CompressionFormat"
            },
            "compression_level": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "action": {
              "type": "string",
              "const": "Inspect"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Write reads to FASTQ file(s) as a pipeline step.\n\nReplicates the FASTQ behaviour of the legacy `[output]` section: per-segment\nfiles, interleaved output, stdout, chunking, hashing and compression.",
          "type": "object",
          "properties": {
            "suffix": {
              "description": "Override the file suffix (defaults to `fq`, plus the compression suffix).",
              "type": [
                "string",
                "null"
              ]
            },
            "compression": {
              "$ref": "#/$defs/CompressionFormat"
            },
            "compression_level": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "compression_threads": {
              "type": "integer",
              "format": "uint",
              "minimum": 1
            },
            "output": {
              "description": "Segments to write to individual files. Defaults to all input segments.",
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string"
              }
            },
            "interleave": {
              "description": "Segments to interleave into a single file (or stdout).",
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string"
              }
            },
            "stdout": {
              "type": "boolean"
            },
            "chunksize": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint",
              "minimum": 0
            },
            "output_hash_uncompressed": {
              "type": "boolean"
            },
            "output_hash_compressed": {
              "type": "boolean"
            },
            "action": {
              "type": "string",
              "enum": [
                "OutputFASTQ",
                "OutputFastq",
                "outputfastq"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Write reads to FASTA file(s) as a pipeline step.\n\nLike [`super::OutputFASTQ`] but emits FASTA records (no quality line).",
          "type": "object",
          "properties": {
            "suffix": {
              "description": "Override the file suffix (defaults to `fasta`, plus the compression suffix).",
              "type": [
                "string",
                "null"
              ]
            },
            "compression": {
              "$ref": "#/$defs/CompressionFormat"
            },
            "compression_level": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "compression_threads": {
              "type": "integer",
              "format": "uint",
              "minimum": 1
            },
            "output": {
              "description": "Segments to write to individual files. Defaults to all input segments.",
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string"
              }
            },
            "interleave": {
              "description": "Segments to interleave into a single file (or stdout).",
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string"
              }
            },
            "stdout": {
              "type": "boolean"
            },
            "chunksize": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint",
              "minimum": 0
            },
            "output_hash_uncompressed": {
              "type": "boolean"
            },
            "output_hash_compressed": {
              "type": "boolean"
            },
            "action": {
              "type": "string",
              "enum": [
                "OutputFASTA",
                "OutputFasta",
                "outputfasta"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Replicates the BAM behaviour of the legacy `[output]` section: per-segment\nfiles, interleaved output, chunking and compressed hashing, plus BAM\nauxiliary-tag export (`bam.tags` / `tag_to_bam_tag`) and reference\nassignment (`bam.tag_to_reference`).\n\nThe reference sequences and the shared BAM header are resolved at\nconfig-verify time (see [`resolve_output_bam`]): `references_from_barcodes`\nfrom the named `[barcodes.*]` section, `from_bam` by reading the `@SQ`\nheader of the referenced BAM file. The resolved [`BamSinkOptions`] are\nstashed on the partial and emitted in `declare_output_files`, so the writer\nis opened with the correct header. The auxiliary-tag values and the\nreference assignment per read are applied at write time by the BAM writer.\n\n`bam.merge_demultiplexed` is carried for round-trip; the actual merge of\ndemultiplexed BAMs lives in the binary crate.",
          "type": "object",
          "properties": {
            "suffix": {
              "description": "Override the file suffix (defaults to `bam`).",
              "type": [
                "string",
                "null"
              ]
            },
            "compression_level": {
              "description": "BGZF compression level (0-9).",
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "output": {
              "description": "Segments to write to individual files. Defaults to all input segments.",
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string"
              }
            },
            "interleave": {
              "description": "Segments to interleave into a single file.",
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string"
              }
            },
            "chunksize": {
              "type": [
                "integer",
                "null"
              ],
              "format": "uint",
              "minimum": 0
            },
            "output_hash_compressed": {
              "type": "boolean"
            },
            "comment_separation_char": {
              "description": "Character used to split read names into a BAM name field and a `CO` auxiliary tag.\nDefaults to `' '` (space).  Reads whose names contain this character are split; the\npart after the character is placed in a `CO` tag so it can exceed the 254-byte limit.",
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "tag_to_bam_tag": {
              "description": "Map of fastqrab tag labels to BAM auxiliary tag names\n\nEach key is a fastqrab tag label; each value is the two-character BAM\nauxiliary tag name to write (e.g. `BC`).",
              "type": [
                "object",
                "null"
              ],
              "additionalProperties": {
                "type": "string"
              }
            },
            "tag_to_reference": {
              "description": "Export a fastqrab tag value as the BAM reference name",
              "anyOf": [
                {
                  "$ref": "#/$defs/TagToReference"
                },
                {
                  "type": "null"
                }
              ]
            },
            "merge_demultiplexed": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "index_merged": {
              "description": "Write a BAI index alongside each merged BAM file (default: true).",
              "type": "boolean"
            },
            "action": {
              "type": "string",
              "enum": [
                "OutputBAM",
                "OutputBam",
                "outputbam"
              ]
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Write the combined run report (JSON and/or HTML) as a pipeline step.\n\nReplicates the report-writing behaviour of the legacy `[output]` section\n(`report_json` / `report_html`). The report data is handed to the step in\n[`Step::post_finalize`], after every other step's `finalize` has populated\nthe report collector.\n\nAt most one `OutputReport` step is allowed per pipeline (enforced in config\nvalidation); multiple would share the same collected report data.\n\nReports are keyed by their config label (from [`ReportMetadata::report_labels`],\nindexed by `report_no`); the run/input metadata the legacy `[output]` renderer\nembedded is taken from the [`ReportMetadata`] handed to the step at `init`.",
          "type": "object",
          "properties": {
            "json": {
              "description": "Emit a `{prefix}.json` report.",
              "type": "boolean"
            },
            "html": {
              "description": "Emit a `{prefix}.html` report.",
              "type": "boolean"
            },
            "action": {
              "type": "string",
              "const": "OutputReport"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Create multiple output files based on a tag",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "output_unmatched": {
              "type": "boolean"
            },
            "barcodes": {
              "description": "reference to shared barcodes section (optional for boolean tag mode)",
              "type": [
                "string",
                "null"
              ]
            },
            "in_label_contains_barcode": {
              "type": "boolean"
            },
            "action": {
              "type": "string",
              "const": "Demultiplex"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "type": "object",
          "properties": {
            "in_label": {
              "description": "Input tag to correct",
              "type": "string"
            },
            "out_label": {
              "description": "Output tag to store corrected result",
              "type": "string"
            },
            "barcodes": {
              "description": "Reference to barcodes section",
              "type": "string"
            },
            "max_hamming_distance": {
              "description": "Maximum hamming distance for correction",
              "type": "integer",
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "output": {
              "$ref": "#/$defs/HammingOutput"
            },
            "on_no_match": {
              "description": "What to do when no match is found",
              "$ref": "#/$defs/OnNoMatch"
            },
            "on_tie": {
              "description": "What to do when more than one match an the same distance is found",
              "$ref": "#/$defs/OnTie"
            },
            "on_tie_min_molecules_to_start": {
              "type": "integer",
              "format": "uint",
              "minimum": 0
            },
            "on_tie_threshold": {
              "type": "number",
              "format": "double"
            },
            "on_tie_use_counts_from_report": {
              "anyOf": [
                {
                  "$ref": "#/$defs/CountsFromReport"
                },
                {
                  "type": "null"
                }
              ]
            },
            "name_split_character": {
              "description": "names are considered identical if they match up to the first `name_split_character`",
              "type": [
                "integer",
                "null"
              ],
              "format": "uint8",
              "minimum": 0,
              "maximum": 255
            },
            "action": {
              "type": "string",
              "const": "HammingCorrect"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "This transformation counts exact hamming matches until we've got enough counts to over to\n`HammingCorrect` in `ByMajority` mode.\n\nIt's created in `expand_transformations`",
          "type": "object",
          "properties": {
            "in_label": {
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "_HammingExactCounter"
            }
          },
          "required": [
            "action"
          ]
        },
        {
          "description": "Assign each query sequence to the closest entry in a named reference database.\n\nThe reference is a FASTA or FASTQ file (optionally gzip-compressed).  Every\nsequence in the file must have the same length as the query.  The step\nbuilds a Hamming-distance index at initialisation and stores the matched\nreference name as a string tag.  When no entry is within `max_hamming_distance`\nthe tag is set to `Missing`, which lets a downstream `FilterByTag` discard\nunassigned reads.",
          "type": "object",
          "properties": {
            "in_label": {
              "description": "Tag containing the query sequence (must be a String or Location tag\nwhose sequences have the same length as the reference entries).",
              "type": "string"
            },
            "out_label": {
              "description": "Output tag where the matched reference name is stored.",
              "type": "string"
            },
            "barcodes": {
              "description": "Reference to barcodes section",
              "type": "string"
            },
            "action": {
              "type": "string",
              "const": "AssignByHalves"
            }
          },
          "required": [
            "action"
          ]
        }
      ]
    },
    "CutStart": {
      "description": "Cut a fixed number of bases from the start of reads",
      "type": "object",
      "properties": {
        "n": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "segment": {
          "type": "string"
        },
        "if_tag": {
          "type": [
            "string",
            "null"
          ]
        },
        "if_label": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "n",
        "segment"
      ]
    },
    "CutEnd": {
      "description": "Cut a fixed number of bases from the end of reads",
      "type": "object",
      "properties": {
        "n": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "segment": {
          "type": "string"
        },
        "if_tag": {
          "type": [
            "string",
            "null"
          ]
        },
        "if_label": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "n",
        "segment"
      ]
    },
    "Truncate": {
      "description": "Truncate reads to a fixed length",
      "type": "object",
      "properties": {
        "n": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "segment": {
          "type": "string"
        },
        "if_tag": {
          "type": [
            "string",
            "null"
          ]
        },
        "if_label": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "n",
        "segment"
      ]
    },
    "PhredEncoding": {
      "type": "string",
      "enum": [
        "Sanger",
        "Solexa",
        "Illumina13",
        "sanger",
        "illumina1.8",
        "solexa",
        "illumina1.3",
        "illumina13"
      ]
    },
    "Prefix": {
      "description": "add a fixed prefix to the start of reads",
      "type": "object",
      "properties": {
        "segment": {
          "type": "string"
        },
        "seq": {
          "type": "string"
        },
        "qual": {
          "type": "string"
        },
        "encoding": {
          "$ref": "#/$defs/PhredEncoding"
        },
        "if_tag": {
          "type": [
            "string",
            "null"
          ]
        },
        "if_label": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "segment",
        "seq",
        "qual",
        "encoding"
      ]
    },
    "Postfix": {
      "description": "Add a fixed sequence to the end of reads",
      "type": "object",
      "properties": {
        "segment": {
          "type": "string"
        },
        "seq": {
          "type": "string"
        },
        "qual": {
          "type": "string"
        },
        "encoding": {
          "$ref": "#/$defs/PhredEncoding"
        },
        "if_tag": {
          "type": [
            "string",
            "null"
          ]
        },
        "if_label": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "segment",
        "seq",
        "qual",
        "encoding"
      ]
    },
    "ConvertQuality": {
      "description": "Convert PHRED scores between encodings",
      "type": "object",
      "properties": {
        "from": {
          "$ref": "#/$defs/PhredEncoding"
        },
        "to": {
          "$ref": "#/$defs/PhredEncoding"
        }
      },
      "required": [
        "from",
        "to"
      ]
    },
    "ReverseComplement": {
      "description": "Reverse complement a read",
      "type": "object",
      "properties": {
        "target": {
          "type": "string"
        },
        "if_tag": {
          "type": [
            "string",
            "null"
          ]
        },
        "segment": {
          "type": "string"
        },
        "source": {
          "type": "string"
        },
        "if_label": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "target"
      ]
    },
    "Rename": {
      "description": "Rename (and/or renumber) reads by applying a regex",
      "type": "object",
      "properties": {
        "search": {
          "type": "string"
        },
        "replacement": {
          "type": "string"
        }
      },
      "required": [
        "search",
        "replacement"
      ]
    },
    "Swap": {
      "description": "Swap two segments.\n\nWith `if_tag` set, the swap is per-read (only reads where the tag is truthy\nexchange their mates). A per-read swap moves a read to the *other* segment,\nso any location tag captured against either swapped segment is **forgotten**\n— a location column may alias only one segment, and a per-read swap would\nsplit it across two. To carry such data across a conditional swap, snapshot\nit to a plain String with `ConcatTags` beforehand. An unconditional swap\nkeeps location tags: their `source_id` is simply re-stamped to the segment\nthat now holds their bytes.",
      "type": "object",
      "properties": {
        "if_tag": {
          "type": [
            "string",
            "null"
          ]
        },
        "segment_a": {
          "type": "string"
        },
        "segment_b": {
          "type": "string"
        },
        "if_label": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "segment_a",
        "segment_b"
      ]
    },
    "Lowercase": {
      "description": "Convert a read, name, tag into lower case",
      "type": "object",
      "properties": {
        "target": {
          "type": "string"
        },
        "if_tag": {
          "type": [
            "string",
            "null"
          ]
        },
        "source": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        },
        "if_label": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "target"
      ]
    },
    "Uppercase": {
      "description": "Convert a read, name, tag into upper case",
      "type": "object",
      "properties": {
        "target": {
          "type": "string"
        },
        "if_tag": {
          "type": [
            "string",
            "null"
          ]
        },
        "segment": {
          "type": "string"
        },
        "source": {
          "type": "string"
        },
        "if_label": {
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "target"
      ]
    },
    "Direction": {
      "type": "string",
      "enum": [
        "Start",
        "End",
        "start",
        "end"
      ]
    },
    "TrimAtTag": {
      "description": "Trim reads at a tag's position",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        },
        "direction": {
          "$ref": "#/$defs/Direction"
        },
        "keep_tag": {
          "type": "boolean"
        }
      },
      "required": [
        "in_label",
        "direction",
        "keep_tag"
      ]
    },
    "Algorithm": {
      "description": "Algorithm to use for scoring overlaps and resolving mismatches",
      "oneOf": [
        {
          "description": "fastp algorithm: quality-score based mismatch resolution\nUses hamming distance for overlap detection and chooses higher quality base for mismatches",
          "type": "string",
          "enum": [
            "Fastp",
            "FastpSeemsWeird",
            "fastp",
            "fastpseemsweird"
          ]
        }
      ]
    },
    "NoOverlapStrategy": {
      "description": "Strategy when reads cannot be merged due to insufficient overlap",
      "oneOf": [
        {
          "description": "Keep reads as they are (no merging)",
          "type": "string",
          "const": "AsIs"
        },
        {
          "description": "Concatenate reads with spacer into first segment",
          "type": "string",
          "const": "Concatenate"
        }
      ]
    },
    "MergeReads": {
      "description": "Merge paired end reads if they're overlapping",
      "type": "object",
      "properties": {
        "algorithm": {
          "description": "Algorithm to use for overlap scoring and mismatch resolution",
          "$ref": "#/$defs/Algorithm"
        },
        "min_overlap": {
          "description": "Minimum overlap length required for merging (suggested: 30, minimum: 5)",
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "max_mismatch_rate": {
          "description": "Maximum allowed mismatch rate (0.0 to 1.0, suggested: 0.2)",
          "type": "number",
          "format": "double"
        },
        "max_mismatch_count": {
          "description": "Maximum allowed absolute number of mismatches (suggested: 5)",
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "no_overlap_strategy": {
          "description": "Strategy when no overlap is found",
          "$ref": "#/$defs/NoOverlapStrategy"
        },
        "out_label": {
          "description": "Tag label to store merge status (suggested: \"merged\")\nTag will be true if reads were merged, false otherwise",
          "type": [
            "string",
            "null"
          ]
        },
        "concatenate_spacer": {
          "description": "Spacer sequence to use when concatenating (required if `no_overlap_strategy` = 'concatenate')",
          "type": [
            "string",
            "null"
          ]
        },
        "spacer_quality_char": {
          "description": "Quality score to use for spacer bases (suggested: 33, which is Phred quality 0)",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "reverse_complement_segment2": {
          "description": "Whether to reverse complement segment2 before merging",
          "type": "boolean"
        },
        "segment1": {
          "type": "string"
        },
        "segment2": {
          "type": "string"
        }
      },
      "required": [
        "algorithm",
        "min_overlap",
        "max_mismatch_rate",
        "max_mismatch_count",
        "no_overlap_strategy",
        "reverse_complement_segment2",
        "segment1",
        "segment2"
      ]
    },
    "TagMatches": {
      "type": "object",
      "properties": {
        "source": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "accepted": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "source",
        "out_label",
        "accepted"
      ]
    },
    "KeepOrRemove": {
      "type": "string",
      "enum": [
        "Keep",
        "Remove",
        "keep",
        "remove"
      ]
    },
    "ByTag": {
      "description": "Filter reads by presence/value of a (non-numeric) tag",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        },
        "keep_or_remove": {
          "$ref": "#/$defs/KeepOrRemove"
        }
      },
      "required": [
        "in_label",
        "keep_or_remove"
      ]
    },
    "ByNumericTag": {
      "description": "Filter reads by threshold on a (numeric) tag",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        },
        "min_value": {
          "type": [
            "number",
            "null"
          ],
          "format": "double"
        },
        "max_value": {
          "type": [
            "number",
            "null"
          ],
          "format": "double"
        },
        "keep_or_remove": {
          "$ref": "#/$defs/KeepOrRemove"
        }
      },
      "required": [
        "in_label",
        "keep_or_remove"
      ]
    },
    "Head": {
      "description": "Filter all reads after the first n",
      "type": "object",
      "properties": {
        "n": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        }
      },
      "required": [
        "n"
      ]
    },
    "Skip": {
      "description": "Skip the first n reads",
      "type": "object",
      "properties": {
        "n": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        }
      },
      "required": [
        "n"
      ]
    },
    "Empty": {
      "description": "Filter empty reads (without sequence, length == 0)",
      "type": "object",
      "properties": {
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "segment"
      ]
    },
    "Sample": {
      "description": "Sample reads by probability. Cheap.",
      "type": "object",
      "properties": {
        "p": {
          "type": "number",
          "format": "double"
        },
        "seed": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0
        }
      },
      "required": [
        "p",
        "seed"
      ]
    },
    "ReservoirSample": {
      "description": "Fairly sample reads (expensive!)",
      "type": "object",
      "properties": {
        "n": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "seed": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0
        }
      },
      "required": [
        "n",
        "seed"
      ]
    },
    "ValidateReadPairing": {
      "description": "Spot check the read names matching across segments",
      "type": "object",
      "properties": {
        "sample_stride": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0
        }
      },
      "required": [
        "sample_stride"
      ]
    },
    "ValidateSeq": {
      "description": "Validate that the sequence is only consisting of the specified bases",
      "type": "object",
      "properties": {
        "allowed": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "allowed",
        "segment"
      ]
    },
    "ValidateQuality": {
      "description": "Validate that quality scores are within Sanger (PHRED 33) range.",
      "type": "object",
      "properties": {
        "encoding": {
          "$ref": "#/$defs/PhredEncoding"
        },
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "encoding",
        "segment"
      ]
    },
    "ValidateName": {
      "description": "Validate that read names between segments match",
      "type": "object",
      "properties": {
        "readname_end_char": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "sample_stride": {
          "type": "integer",
          "format": "uint64",
          "minimum": 0
        }
      },
      "required": [
        "sample_stride"
      ]
    },
    "ValidateAllReadsSameLength": {
      "description": "Verify that all reads have the same length",
      "type": "object",
      "properties": {
        "source": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "source"
      ]
    },
    "ValidateReadNamesPrintable": {
      "description": "Validate that read names conform to the SAM/BAM specification.\n\nValid read names match `[!-?A-~]{1,254}` — printable ASCII excluding `@` and\nspace, with a maximum length of 254 characters.",
      "type": "object"
    },
    "Anchor": {
      "description": "Where to search",
      "type": "string",
      "enum": [
        "Left",
        "Right",
        "Anywhere",
        "left",
        "right",
        "anywhere"
      ]
    },
    "TieBreak": {
      "type": "string",
      "enum": [
        "Earliest",
        "LeftMost",
        "RightMost",
        "earliest",
        "leftmost",
        "rightmost"
      ]
    },
    "IUPAC": {
      "description": "Extract a IUPAC described sequence from the read. E.g. an adapter.\nCan be at the start (anchor = Left, the end (anchor = Right),\nor anywhere (anchor = Anywhere) within the read.\nThe search parameter can be either a single IUPAC string or a list of IUPAC strings.\nIf multiple strings are provided, the first hit wins.",
      "type": "object",
      "properties": {
        "search": {
          "$ref": "#/$defs/StringOrVecString"
        },
        "segment": {
          "type": "string"
        },
        "anchor": {
          "$ref": "#/$defs/Anchor"
        },
        "max_anchor_distance": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "out_label": {
          "type": "string"
        },
        "max_mismatches": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "on_tie": {
          "$ref": "#/$defs/TieBreak"
        },
        "query": {
          "$ref": "#/$defs/StringOrVecString"
        },
        "pattern": {
          "$ref": "#/$defs/StringOrVecString"
        }
      },
      "required": [
        "search",
        "segment",
        "anchor",
        "max_anchor_distance",
        "out_label",
        "max_mismatches",
        "on_tie"
      ]
    },
    "IUPACWithIndel": {
      "description": "Extract an IUPAC-described sequence while tolerating insertions and deletions.\nUseful for adapters where small indels are expected.",
      "type": "object",
      "properties": {
        "search": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        },
        "anchor": {
          "$ref": "#/$defs/Anchor"
        },
        "out_label": {
          "type": "string"
        },
        "max_mismatches": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "max_indel_bases": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "max_total_edits": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "pattern": {
          "type": "string"
        },
        "query": {
          "type": "string"
        }
      },
      "required": [
        "search",
        "segment",
        "anchor",
        "out_label",
        "max_mismatches",
        "max_indel_bases"
      ]
    },
    "Regex": {
      "description": "Region by regular expression",
      "type": "object",
      "properties": {
        "search": {
          "type": "string"
        },
        "replacement": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "source": {
          "type": "string"
        },
        "pattern": {
          "type": "string"
        },
        "query": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "search",
        "replacement",
        "out_label",
        "source"
      ]
    },
    "RegionAnchor": {
      "type": "string",
      "enum": [
        "Start",
        "End",
        "left",
        "right",
        "start",
        "end"
      ]
    },
    "Region": {
      "description": "Define a region by coordinates",
      "type": "object",
      "properties": {
        "start": {
          "type": "integer",
          "format": "int"
        },
        "len": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "source": {
          "description": "Source for extraction - segment name, \"tag:name\" for tag source, or \"name:segment\" for read name source",
          "type": "string"
        },
        "anchor": {
          "description": "Is the region from the `Start` or the `End` of the source?",
          "$ref": "#/$defs/RegionAnchor"
        },
        "out_label": {
          "type": "string"
        },
        "length": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "segment": {
          "description": "Source for extraction - segment name, \"tag:name\" for tag source, or \"name:segment\" for read name source",
          "type": "string"
        }
      },
      "required": [
        "start",
        "len",
        "source",
        "anchor",
        "out_label"
      ]
    },
    "RegionDefinition": {
      "type": "object",
      "properties": {
        "start": {
          "type": "integer",
          "format": "int"
        },
        "length": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "anchor": {
          "description": "Whether the start position is anchored to the start (default) or end of the region",
          "$ref": "#/$defs/RegionAnchor"
        },
        "len": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "count": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "n": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        }
      },
      "required": [
        "start",
        "length",
        "anchor"
      ]
    },
    "Regions": {
      "description": "Extract regions by coordinates\nthat is by (segment|source, 0-based start, length)\ndefined triplets, joined with (possibly empty) separator.",
      "type": "object",
      "properties": {
        "regions": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/RegionDefinition"
          }
        },
        "source": {
          "description": "Source for extraction - segment name, \"tag:name\" for tag source, or \"name:segment\" for read name source",
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "segment": {
          "description": "Source for extraction - segment name, \"tag:name\" for tag source, or \"name:segment\" for read name source",
          "type": "string"
        }
      },
      "required": [
        "regions",
        "source",
        "out_label"
      ]
    },
    "Length": {
      "description": "Convert read length into a tag",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "out_label",
        "segment"
      ]
    },
    "BaseContent": {
      "description": "Quantify base occurrence rate or count",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        },
        "relative": {
          "type": "boolean"
        },
        "bases_to_count": {
          "type": "string"
        },
        "bases_to_ignore": {
          "type": "string"
        }
      },
      "required": [
        "out_label",
        "segment",
        "relative",
        "bases_to_count",
        "bases_to_ignore"
      ]
    },
    "GCContent": {
      "description": "Calculate (relative) GC content. See `CalcBaseContent`",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        },
        "relative": {
          "type": "boolean"
        },
        "rate": {
          "type": "boolean"
        }
      },
      "required": [
        "out_label",
        "segment",
        "relative"
      ]
    },
    "NContent": {
      "description": "Count the number of N. See `CalcBaseContent` for general case",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        },
        "relative": {
          "type": "boolean"
        },
        "rate": {
          "type": "boolean"
        }
      },
      "required": [
        "out_label",
        "segment",
        "relative"
      ]
    },
    "Complexity": {
      "description": "Calculate complexity score (# transitions / (len -1))",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "out_label",
        "segment"
      ]
    },
    "Operator": {
      "type": "string",
      "enum": [
        "Above",
        "Below",
        "AboveOrEqual",
        "BelowOrEqual",
        ">",
        "Worse",
        "gt",
        "<",
        "Better",
        "lt",
        ">=",
        "WorseOrEqual",
        "gte",
        "<=",
        "BetterOrEqual",
        "lte",
        "above",
        "below",
        "aboveorequal",
        "beloworequal",
        "worse",
        "better",
        "worseorequal",
        "betterorequal"
      ]
    },
    "QualifiedBases": {
      "description": "Calculate bases passing quality threshold (in any direction)",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "threshold": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "operator": {
          "$ref": "#/$defs/Operator"
        },
        "relative": {
          "type": "boolean"
        },
        "segment": {
          "type": "string"
        },
        "op": {
          "$ref": "#/$defs/Operator"
        },
        "rate": {
          "type": "boolean"
        }
      },
      "required": [
        "out_label",
        "threshold",
        "operator",
        "relative",
        "segment"
      ]
    },
    "ExpectedErrorAggregate": {
      "type": "string",
      "enum": [
        "Sum",
        "Max",
        "sum",
        "max"
      ]
    },
    "ExpectedError": {
      "description": "Calculate expected error from (sanger, 33 based) PHRED",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        },
        "aggregate": {
          "$ref": "#/$defs/ExpectedErrorAggregate"
        }
      },
      "required": [
        "out_label",
        "segment",
        "aggregate"
      ]
    },
    "WorstQuality": {
      "description": "Calculate minimum quality byte value across a segment or a tagged region",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "source": {
          "type": "string"
        },
        "offset": {
          "type": "integer",
          "format": "int8",
          "minimum": -128,
          "maximum": 127
        },
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "out_label",
        "source",
        "offset"
      ]
    },
    "Kmers": {
      "description": "Quantify Kmer occurance vs database",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        },
        "filename": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "k": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "count_reverse_complement": {
          "type": "boolean"
        },
        "min_count": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "files": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "filenames": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "canonical": {
          "type": "boolean"
        }
      },
      "required": [
        "out_label",
        "segment",
        "filename",
        "k",
        "count_reverse_complement",
        "min_count"
      ]
    },
    "ConvertToRate": {
      "description": "Divide a numeric tag value by the read length to produce a rate (0..=1)",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "segment": {
          "type": "string"
        }
      },
      "required": [
        "in_label",
        "out_label",
        "segment"
      ]
    },
    "RegionsToLength": {
      "description": "Convert region tag to a length tag",
      "type": "object",
      "properties": {
        "out_label": {
          "type": "string"
        },
        "in_label": {
          "type": "string"
        }
      },
      "required": [
        "out_label",
        "in_label"
      ]
    },
    "ResultType": {
      "type": "string",
      "enum": [
        "Numeric",
        "Bool",
        "number",
        "float",
        "boolean",
        "numeric",
        "bool"
      ]
    },
    "EvalExpression": {
      "description": "Evaluate an equation on tags",
      "type": "object",
      "properties": {
        "out_label": {
          "description": "The tag label to store the result",
          "type": "string"
        },
        "expression": {
          "description": "The arithmetic or boolean expression to evaluate.\nVariables in the expression should match existing tag names.\nExpressions using comparison or logical operators (`<`, `>`, `==`, `!=`, `&&`, `||`, `!`)\nproduce a bool tag; pure arithmetic expressions produce a numeric tag.",
          "type": "string"
        },
        "result_type": {
          "anyOf": [
            {
              "$ref": "#/$defs/ResultType"
            },
            {
              "type": "null"
            }
          ]
        },
        "expr": {
          "description": "The arithmetic or boolean expression to evaluate.\nVariables in the expression should match existing tag names.\nExpressions using comparison or logical operators (`<`, `>`, `==`, `!=`, `&&`, `||`, `!`)\nproduce a bool tag; pure arithmetic expressions produce a numeric tag.",
          "type": "string"
        },
        "query": {
          "description": "The arithmetic or boolean expression to evaluate.\nVariables in the expression should match existing tag names.\nExpressions using comparison or logical operators (`<`, `>`, `==`, `!=`, `&&`, `||`, `!`)\nproduce a bool tag; pure arithmetic expressions produce a numeric tag.",
          "type": "string"
        },
        "out_type": {
          "anyOf": [
            {
              "$ref": "#/$defs/ResultType"
            },
            {
              "type": "null"
            }
          ]
        },
        "output_type": {
          "anyOf": [
            {
              "$ref": "#/$defs/ResultType"
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "out_label",
        "expression"
      ]
    },
    "CompareStringTags": {
      "description": "Compare two string- or location-valued tags lexicographically (byte-by-byte).\n\nReturns -1.0 if `tag_a` < `tag_b`, 0.0 if equal, 1.0 if `tag_a` > `tag_b`,\nlike Rust's `PartialOrd` on byte slices. Missing on either input → Missing output.\nRaises a runtime error if the two byte sequences for a read have different lengths.",
      "type": "object",
      "properties": {
        "in_label_a": {
          "type": "string"
        },
        "in_label_b": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        }
      },
      "required": [
        "in_label_a",
        "in_label_b",
        "out_label"
      ]
    },
    "RegionsOfLowQuality": {
      "description": "Extract regions of low quality (configurable)",
      "type": "object",
      "properties": {
        "segment": {
          "type": "string"
        },
        "min_quality": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "min_length": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "out_label": {
          "type": "string"
        }
      },
      "required": [
        "segment",
        "min_quality",
        "min_length",
        "out_label"
      ]
    },
    "LongestPolyX": {
      "description": "Find the longest polyX",
      "type": "object",
      "properties": {
        "segment": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "min_length": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "base": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "max_mismatch_rate": {
          "type": "number",
          "format": "double"
        },
        "max_consecutive_mismatches": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        }
      },
      "required": [
        "segment",
        "out_label",
        "min_length",
        "base",
        "max_mismatch_rate",
        "max_consecutive_mismatches"
      ]
    },
    "PolyTail": {
      "description": "Extract ends that are homo-polymers into a tag",
      "type": "object",
      "properties": {
        "segment": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "min_length": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "base": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "max_mismatch_rate": {
          "type": "number",
          "format": "double"
        },
        "max_consecutive_mismatches": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "fastp_mode": {
          "description": "Use fastp's polyG trimming algorithm (requires base = 'G').\nApplies fastp's fixed constants: 1 mismatch allowed per 8 bases, max 5 total mismatches.",
          "type": [
            "boolean",
            "null"
          ]
        }
      },
      "required": [
        "segment",
        "out_label",
        "min_length",
        "base",
        "max_mismatch_rate",
        "max_consecutive_mismatches"
      ]
    },
    "IUPACSuffix": {
      "description": "Extract a IUPAC sequence (or a prefix of it) at the end of a read into a tag.",
      "type": "object",
      "properties": {
        "segment": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "min_length": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "max_mismatches": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "search": {
          "type": "string"
        },
        "query": {
          "type": "string"
        },
        "pattern": {
          "type": "string"
        }
      },
      "required": [
        "segment",
        "out_label",
        "min_length",
        "max_mismatches",
        "search"
      ]
    },
    "LowQualityStart": {
      "description": "Turn low quality start's of reads into a tag",
      "type": "object",
      "properties": {
        "segment": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "min_qual": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        }
      },
      "required": [
        "segment",
        "out_label",
        "min_qual"
      ]
    },
    "LowQualityEnd": {
      "description": "Turn low quality end's of reads into a tag",
      "type": "object",
      "properties": {
        "segment": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "min_qual": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        }
      },
      "required": [
        "segment",
        "out_label",
        "min_qual"
      ]
    },
    "Duplicates": {
      "description": "Tag duplicate reads",
      "type": "object",
      "properties": {
        "source": {
          "type": "string"
        },
        "out_label": {
          "type": "string"
        },
        "false_positive_rate": {
          "type": "number",
          "format": "double"
        },
        "seed": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint64",
          "minimum": 0
        },
        "initial_filter_capacity": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        }
      },
      "required": [
        "source",
        "out_label",
        "false_positive_rate"
      ]
    },
    "OtherFile": {
      "description": "Tag whether reads are in another file (by sequence)",
      "type": "object",
      "properties": {
        "filename": {
          "type": "string"
        },
        "source": {
          "type": "string"
        },
        "other_readname_end_character": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "out_label": {
          "type": "string"
        },
        "seed": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint64",
          "minimum": 0
        },
        "false_positive_rate": {
          "type": "number",
          "format": "double"
        },
        "include_mapped": {
          "type": "boolean"
        },
        "include_unmapped": {
          "type": "boolean"
        },
        "segment": {
          "type": "string"
        },
        "other_read_name_end_char": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        }
      },
      "required": [
        "filename",
        "source",
        "out_label",
        "false_positive_rate",
        "include_mapped",
        "include_unmapped"
      ]
    },
    "OnLost": {
      "description": "What to do when a write-back's captured coordinates can no longer be located\nin the read — the span was cut away or split by an edit applied *after* the\ntag was captured, so there is nowhere to write.",
      "oneOf": [
        {
          "description": "Silently leave the affected read unchanged (the default).",
          "type": "string",
          "const": "Ignore"
        },
        {
          "description": "Abort the run with an error naming the affected reads.",
          "type": "string",
          "const": "Complain"
        }
      ]
    },
    "StoreTagBackInSequence": {
      "description": "Store the tag's 'sequence', probably modified by a previous step,\nback into the reads' sequence.\n\nThis is exactly [`StoreTagInSequence`] with the tag used as both the content\nsource and the position (anchor `Replace`): the tag's (possibly modified) bytes\noverwrite the location they came from. An unmodified location tag is already the\nread's own bytes, so writing it back is a no-op; a tag whose content diverged\n(a regex replacement, a reverse-complement or case change) replaces those bytes.\nIts quality travels with it.\n\nIf the location is a single span the content may be longer or shorter than what\nit replaces (the read grows or shrinks); if it covers several disjoint regions\nthe content must match their total length. The location is lifted through any\nedits applied since it was captured, so the content lands where the tag *now*\nsits in the read. If an intervening edit cut that location away, `on_lost`\ndecides what happens (default: leave the read unchanged).\n\n[`StoreTagInSequence`]: super::store_tag_in_sequence::StoreTagInSequence",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        },
        "on_lost": {
          "description": "What to do when the tag's location was lost to a later edit.",
          "$ref": "#/$defs/OnLost"
        },
        "on_loss": {
          "description": "What to do when the tag's location was lost to a later edit.",
          "$ref": "#/$defs/OnLost"
        }
      },
      "required": [
        "in_label",
        "on_lost"
      ]
    },
    "ReplacementAnchor": {
      "type": "string",
      "enum": [
        "Start",
        "End",
        "Replace",
        "left",
        "right",
        "start",
        "end",
        "replace"
      ]
    },
    "StoreTagInSequence": {
      "description": "Insert a tag's string value into a read sequence at the position defined by another\nlocation tag.\n\n`in_value_label` must be a location or string tag — its byte sequence (via `as_ref()` on\nthe value) is the text inserted into the read.\n\nA location value carries its own (possibly edited) quality with it; a string value has\nno quality, so its inserted bases get `~` (Phred 93, maximum Sanger quality).\n\n`in_position_label` must be a location tag — it defines *where* to act:\n\n- `anchor = \"Start\"` (alias `\"left\"`) — inserts **before** the leftmost start of the\n  location (the hit with the smallest `start` coordinate).\n- `anchor = \"End\"` (alias `\"right\"`) — inserts **after** the rightmost end of the\n  location (the hit with the largest `start + len` value).\n- `anchor = \"Replace\"` — overwrites the location. A single span (or several regions that\n  lift to a contiguous range) may be replaced by content of any length; several disjoint\n  regions require the content to match their total length, and the content is then laid\n  down byte-for-byte over exactly the covered positions. Any other disjoint case is a\n  runtime error.\n\nAll coordinates are lifted through edits applied since the position tag was captured, and\nthe length change is recorded so later location tags shift correctly.\n\nIf either tag value is `Missing`, the position tag carries no location, or an edit cut the\nposition away (subject to `on_lost`), the read is left unchanged.",
      "type": "object",
      "properties": {
        "in_value_label": {
          "description": "Location or string tag whose sequence to insert into the read.",
          "type": "string"
        },
        "in_position_label": {
          "description": "Location tag that defines the insertion position.",
          "type": "string"
        },
        "anchor": {
          "description": "Where to insert relative to the position tag.",
          "$ref": "#/$defs/ReplacementAnchor"
        },
        "on_lost": {
          "description": "What to do when the position tag's location was lost to a later edit.",
          "$ref": "#/$defs/OnLost"
        },
        "on_loss": {
          "description": "What to do when the position tag's location was lost to a later edit.",
          "$ref": "#/$defs/OnLost"
        }
      },
      "required": [
        "in_value_label",
        "in_position_label",
        "anchor",
        "on_lost"
      ]
    },
    "ReplaceTagWithLetter": {
      "description": "Replace all bases with this (region) tag with one base",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        },
        "letter": {
          "description": "Provide the replacement letter as a single character (e.g., 'N') or its ASCII numeric value (e.g., 78 for 'N').",
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        }
      },
      "required": [
        "in_label",
        "letter"
      ]
    },
    "OnMissing": {
      "description": "Behavior when encountering missing tags during concatenation",
      "oneOf": [
        {
          "description": "Skip missing tags and merge only the present ones",
          "type": "string",
          "const": "MergePresent"
        },
        {
          "description": "Set the output tag to missing if any input tag is missing",
          "type": "string",
          "const": "SetMissing"
        }
      ]
    },
    "ConcatTags": {
      "description": "Concatenate multiple tags into a single tag.\n\nTakes n >= 2 tags (which can be location tags or string tags) and combines them:\n- If all tags are Location: appends regions and concatenates sequences\n- If all tags are String: concatenates strings with optional separator\n- If mixed (Location + String): converts all to strings and concatenates\n\n# Examples\n\n```toml\n[[step]]\naction = \"ConcatTags\"\nin_labels = [\"barcode1\", \"barcode2\"]\nout_label = \"combined_barcode\"\nseparator = \"_\"  # Optional, only used for string concatenation\n```",
      "type": "object",
      "properties": {
        "in_labels": {
          "description": "Input tag labels to concatenate (must have at least 2)",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "out_label": {
          "description": "Output tag label for the concatenated result",
          "type": "string"
        },
        "separator": {
          "description": "Separator to use when concatenating strings (optional, defaults to empty)",
          "type": "string"
        },
        "on_missing": {
          "description": "Behavior when encountering missing tags\n- `merge_present`: Skip missing tags and merge only the present ones\n- `set_missing`: Set the output tag to missing if any input tag is missing",
          "$ref": "#/$defs/OnMissing"
        }
      },
      "required": [
        "in_labels",
        "out_label",
        "separator",
        "on_missing"
      ]
    },
    "FillMissing": {
      "description": "Return the primary tag if it is not missing, otherwise return the secondary tag.\n\nBoth tags must have the same kind (Location or String), or be a\nLocation/String mixture — in that case the output is always a String tag\n(Location values are converted to their sequence representation).",
      "type": "object",
      "properties": {
        "in_label_primary": {
          "description": "Primary tag: used when not missing.",
          "type": "string"
        },
        "in_label_secondary": {
          "description": "Secondary tag: used when the primary tag is missing.",
          "type": "string"
        },
        "out_label": {
          "description": "Output tag label for the result.",
          "type": "string"
        },
        "primary": {
          "description": "Primary tag: used when not missing.",
          "type": "string"
        },
        "first": {
          "description": "Primary tag: used when not missing.",
          "type": "string"
        },
        "secondary": {
          "description": "Secondary tag: used when the primary tag is missing.",
          "type": "string"
        },
        "second": {
          "description": "Secondary tag: used when the primary tag is missing.",
          "type": "string"
        }
      },
      "required": [
        "in_label_primary",
        "in_label_secondary",
        "out_label"
      ]
    },
    "ForgetAllTags": {
      "description": "Remove all tags from memory",
      "type": "object"
    },
    "ForgetTag": {
      "description": "remove one tag from memory",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        }
      },
      "required": [
        "in_label"
      ]
    },
    "StoreTagInComment": {
      "description": "Store currently present tags as comments on read names.\nComments are key=value pairs, separated by `comment_separator`\nwhich defaults to '|'.\nThey get inserted at the first `comment_insert_char`,\nwhich defaults to space. The `comment_insert_char` basically moves\nto the right.\n\nThat means a read name like\n@ERR12828869.501 A00627:18:HGV7TDSXX:3:1101:10502:5274/1\nbecomes\n@ERR12828869.501|key=value|key2=value2 A00627:18:HGV7TDSXX:3:1101:10502:5274/1\n\nThis way, your added tags will survive STAR alignment.\n(STAR always cuts at the first space, and by default also on /)\n\n(If the `comment_insert_char` is not present, we simply add at the right)\n\n\nBe default, comments are only placed on Read1.\nIf you need them somewhere else, or an all reads, change the target (to \"All\")",
      "type": "object",
      "properties": {
        "in_labels": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "segment": {
          "type": "string"
        },
        "comment_separator": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "comment_insert_char": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "region_separator": {
          "type": "string"
        },
        "in_label": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "in_labels",
        "segment",
        "comment_separator",
        "comment_insert_char",
        "region_separator"
      ]
    },
    "FileFormat": {
      "oneOf": [
        {
          "type": "string",
          "enum": [
            "Fastq",
            "Fasta",
            "Bam",
            "None",
            "fastq",
            "fasta",
            "bam",
            "none"
          ]
        }
      ]
    },
    "CompressionFormat": {
      "type": "string",
      "enum": [
        "Uncompressed",
        "Gzip",
        "Zstd",
        "uncompressed",
        "raw",
        "gzip",
        "gz",
        "zstd",
        "zst"
      ]
    },
    "StoreTagInFastQ": {
      "description": "Store tag values into FASTQ files.\n\nFiles are named using the pattern: `{output_prefix}_{infix}.tag.fastq.{suffix}`\nWhen demultiplexing: `{output_prefix}_{infix}_{barcode}.tag.fastq.{suffix}`\n\nOptionally adds comment tags to read names before writing, similar to `StoreTagInComment`.",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        },
        "comment_tags": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "comment_separator": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "comment_insert_char": {
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "region_separator": {
          "type": "string"
        },
        "format": {
          "$ref": "#/$defs/FileFormat"
        },
        "compression": {
          "$ref": "#/$defs/CompressionFormat"
        },
        "compression_level": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "comment_labels": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "in_label",
        "comment_separator",
        "comment_insert_char",
        "region_separator",
        "format",
        "compression"
      ]
    },
    "StoreTagsInTable": {
      "description": "Store all currently defined tags in a TSV",
      "type": "object",
      "properties": {
        "infix": {
          "type": [
            "string",
            "null"
          ]
        },
        "compression": {
          "$ref": "#/$defs/CompressionFormat"
        },
        "compression_level": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "region_separator": {
          "type": "string"
        },
        "include_read_name": {
          "type": "boolean"
        },
        "include_read_comment": {
          "type": "boolean"
        },
        "in_labels": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "tags": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "compression",
        "region_separator",
        "include_read_name",
        "include_read_comment"
      ]
    },
    "UMIAggregation": {
      "type": "string",
      "enum": [
        "None",
        "Exact",
        "Cluster",
        "CellRangerLike",
        "1MM",
        "cellranger",
        "none",
        "exact",
        "cluster",
        "cellrangerlike",
        "1mm"
      ]
    },
    "StoreSingleCellMatrix": {
      "description": "Collect (`gene_idx`, `cell_idx`, `umi_2bit`) triples per read, then write binary\ndata file(s) and lookup tables on finalize.\n\nBoth cell and gene barcodes are looked up by sequence in [barcodes.*] tables.\nIndex 0 is reserved for \"unmatched\"; real barcodes are 1-indexed.\nOutput: one binary file per demultiplex group + shared lookup .txt files.",
      "type": "object",
      "properties": {
        "cell_tag": {
          "description": "Tag carrying the cell-barcode sequence (Location or String)",
          "type": "string"
        },
        "gene_tag": {
          "description": "Tag carrying the gene-barcode sequence (Location or String)",
          "type": "string"
        },
        "umi_tag": {
          "description": "Tag carrying the raw UMI sequence (Location or String)",
          "type": "string"
        },
        "umi_aggregation": {
          "$ref": "#/$defs/UMIAggregation"
        },
        "cell_barcodes": {
          "description": "[barcode.*] sec listing all valid cell barcodes (sequence → name)",
          "type": "string"
        },
        "gene_barcodes": {
          "description": "[barcode.*] section listing all valid gene identifiers (sequence → name)",
          "type": "string"
        },
        "cell_tag_contains_barcode": {
          "description": "Whether `cell_tag` values are barcode sequences (true) or corrected labels (false).\nDefault: auto-detect — true for Location tags, false for String tags.",
          "type": [
            "boolean",
            "null"
          ]
        },
        "gene_tag_contains_barcode": {
          "description": "Whether `gene_tag` values are barcode sequences (true) or corrected labels (false).\nDefault: auto-detect — true for Location tags, false for String tags.",
          "type": [
            "boolean",
            "null"
          ]
        },
        "infix": {
          "description": "Infix for output filenames",
          "type": [
            "string",
            "null"
          ]
        },
        "compression": {
          "description": "Compression for the binary data file (lookup tables are always plain text)",
          "$ref": "#/$defs/CompressionFormat"
        },
        "compression_level": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        }
      },
      "required": [
        "cell_tag",
        "gene_tag",
        "umi_tag",
        "umi_aggregation",
        "cell_barcodes",
        "gene_barcodes",
        "compression"
      ]
    },
    "QuantifyTag": {
      "description": "Write a histogram of tag values to a JSON file.",
      "type": "object",
      "properties": {
        "infix": {
          "type": "string"
        },
        "in_label": {
          "type": "string"
        },
        "region_separator": {
          "type": "string"
        }
      },
      "required": [
        "infix",
        "in_label",
        "region_separator"
      ]
    },
    "Progress": {
      "description": "output a progress indicator",
      "type": "object",
      "properties": {
        "n": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "output_infix": {
          "type": "string"
        }
      },
      "required": [
        "n",
        "output_infix"
      ]
    },
    "Report": {
      "description": "Include a report at this position",
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "count": {
          "type": "boolean"
        },
        "base_statistics": {
          "type": "boolean"
        },
        "length_distribution": {
          "type": "boolean"
        },
        "duplicate_count_per_read": {
          "type": "boolean"
        },
        "duplicate_count_per_fragment": {
          "type": "boolean"
        },
        "count_oligos": {
          "type": [
            "object",
            "null"
          ],
          "additionalProperties": {
            "type": "string"
          }
        },
        "count_oligos_segment": {
          "type": "string"
        },
        "tag_histograms": {
          "description": "Generate histograms for specified tags",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "tag_histogram": {
          "description": "Generate histograms for specified tags",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "name",
        "count",
        "base_statistics",
        "length_distribution",
        "duplicate_count_per_read",
        "duplicate_count_per_fragment",
        "count_oligos_segment"
      ]
    },
    "Inspect": {
      "description": "Inspect reads within the workflow",
      "type": "object",
      "properties": {
        "n": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "segment": {
          "type": "string"
        },
        "infix": {
          "type": "string"
        },
        "suffix": {
          "type": [
            "string",
            "null"
          ]
        },
        "format": {
          "$ref": "#/$defs/FileFormat"
        },
        "compression": {
          "$ref": "#/$defs/CompressionFormat"
        },
        "compression_level": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        }
      },
      "required": [
        "n",
        "segment",
        "infix",
        "format",
        "compression"
      ]
    },
    "OutputFASTQ": {
      "description": "Write reads to FASTQ file(s) as a pipeline step.\n\nReplicates the FASTQ behaviour of the legacy `[output]` section: per-segment\nfiles, interleaved output, stdout, chunking, hashing and compression.",
      "type": "object",
      "properties": {
        "suffix": {
          "description": "Override the file suffix (defaults to `fq`, plus the compression suffix).",
          "type": [
            "string",
            "null"
          ]
        },
        "compression": {
          "$ref": "#/$defs/CompressionFormat"
        },
        "compression_level": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "compression_threads": {
          "type": "integer",
          "format": "uint",
          "minimum": 1
        },
        "output": {
          "description": "Segments to write to individual files. Defaults to all input segments.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "interleave": {
          "description": "Segments to interleave into a single file (or stdout).",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "stdout": {
          "type": "boolean"
        },
        "chunksize": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "output_hash_uncompressed": {
          "type": "boolean"
        },
        "output_hash_compressed": {
          "type": "boolean"
        },
        "segments": {
          "description": "Segments to write to individual files. Defaults to all input segments.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "interleaved": {
          "description": "Segments to interleave into a single file (or stdout).",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "compression",
        "compression_threads",
        "stdout",
        "output_hash_uncompressed",
        "output_hash_compressed"
      ]
    },
    "OutputFASTA": {
      "description": "Write reads to FASTA file(s) as a pipeline step.\n\nLike [`super::OutputFASTQ`] but emits FASTA records (no quality line).",
      "type": "object",
      "properties": {
        "suffix": {
          "description": "Override the file suffix (defaults to `fasta`, plus the compression suffix).",
          "type": [
            "string",
            "null"
          ]
        },
        "compression": {
          "$ref": "#/$defs/CompressionFormat"
        },
        "compression_level": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "compression_threads": {
          "type": "integer",
          "format": "uint",
          "minimum": 1
        },
        "output": {
          "description": "Segments to write to individual files. Defaults to all input segments.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "interleave": {
          "description": "Segments to interleave into a single file (or stdout).",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "stdout": {
          "type": "boolean"
        },
        "chunksize": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "output_hash_uncompressed": {
          "type": "boolean"
        },
        "output_hash_compressed": {
          "type": "boolean"
        },
        "segments": {
          "description": "Segments to write to individual files. Defaults to all input segments.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "interleaved": {
          "description": "Segments to interleave into a single file (or stdout).",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "compression",
        "compression_threads",
        "stdout",
        "output_hash_uncompressed",
        "output_hash_compressed"
      ]
    },
    "TagToReference": {
      "description": "Source for reference sequences used by `tag_to_reference` (Feature B).\n\nExactly one of `barcodes` or `from_bam` must be set.",
      "type": "object",
      "properties": {
        "tag": {
          "description": "The fastqrab tag label whose value selects the reference sequence name.",
          "type": "string"
        },
        "references_from_barcodes": {
          "description": "Name of a `[barcodes.<name>]` section whose keys become reference names.",
          "type": [
            "string",
            "null"
          ]
        },
        "references_from_bam": {
          "description": "Path to a BAM file whose `@SQ` header lines define the reference sequences.",
          "type": [
            "string",
            "null"
          ]
        },
        "from_barcodes": {
          "description": "Name of a `[barcodes.<name>]` section whose keys become reference names.",
          "type": [
            "string",
            "null"
          ]
        },
        "from_bam": {
          "description": "Path to a BAM file whose `@SQ` header lines define the reference sequences.",
          "type": [
            "string",
            "null"
          ]
        },
        "template": {
          "description": "Path to a BAM file whose `@SQ` header lines define the reference sequences.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "tag"
      ]
    },
    "OutputBAM": {
      "description": "Replicates the BAM behaviour of the legacy `[output]` section: per-segment\nfiles, interleaved output, chunking and compressed hashing, plus BAM\nauxiliary-tag export (`bam.tags` / `tag_to_bam_tag`) and reference\nassignment (`bam.tag_to_reference`).\n\nThe reference sequences and the shared BAM header are resolved at\nconfig-verify time (see [`resolve_output_bam`]): `references_from_barcodes`\nfrom the named `[barcodes.*]` section, `from_bam` by reading the `@SQ`\nheader of the referenced BAM file. The resolved [`BamSinkOptions`] are\nstashed on the partial and emitted in `declare_output_files`, so the writer\nis opened with the correct header. The auxiliary-tag values and the\nreference assignment per read are applied at write time by the BAM writer.\n\n`bam.merge_demultiplexed` is carried for round-trip; the actual merge of\ndemultiplexed BAMs lives in the binary crate.",
      "type": "object",
      "properties": {
        "suffix": {
          "description": "Override the file suffix (defaults to `bam`).",
          "type": [
            "string",
            "null"
          ]
        },
        "compression_level": {
          "description": "BGZF compression level (0-9).",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "output": {
          "description": "Segments to write to individual files. Defaults to all input segments.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "interleave": {
          "description": "Segments to interleave into a single file.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "chunksize": {
          "type": [
            "integer",
            "null"
          ],
          "format": "uint",
          "minimum": 0
        },
        "output_hash_compressed": {
          "type": "boolean"
        },
        "comment_separation_char": {
          "description": "Character used to split read names into a BAM name field and a `CO` auxiliary tag.\nDefaults to `' '` (space).  Reads whose names contain this character are split; the\npart after the character is placed in a `CO` tag so it can exceed the 254-byte limit.",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "tag_to_bam_tag": {
          "description": "Map of fastqrab tag labels to BAM auxiliary tag names\n\nEach key is a fastqrab tag label; each value is the two-character BAM\nauxiliary tag name to write (e.g. `BC`).",
          "type": [
            "object",
            "null"
          ],
          "additionalProperties": {
            "type": "string"
          }
        },
        "tag_to_reference": {
          "description": "Export a fastqrab tag value as the BAM reference name",
          "anyOf": [
            {
              "$ref": "#/$defs/TagToReference"
            },
            {
              "type": "null"
            }
          ]
        },
        "merge_demultiplexed": {
          "type": [
            "boolean",
            "null"
          ]
        },
        "index_merged": {
          "description": "Write a BAI index alongside each merged BAM file (default: true).",
          "type": "boolean"
        },
        "segments": {
          "description": "Segments to write to individual files. Defaults to all input segments.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "interleaved": {
          "description": "Segments to interleave into a single file.",
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          }
        },
        "tags": {
          "description": "Map of fastqrab tag labels to BAM auxiliary tag names\n\nEach key is a fastqrab tag label; each value is the two-character BAM\nauxiliary tag name to write (e.g. `BC`).",
          "type": [
            "object",
            "null"
          ],
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "required": [
        "output_hash_compressed",
        "index_merged"
      ]
    },
    "OutputReport": {
      "description": "Write the combined run report (JSON and/or HTML) as a pipeline step.\n\nReplicates the report-writing behaviour of the legacy `[output]` section\n(`report_json` / `report_html`). The report data is handed to the step in\n[`Step::post_finalize`], after every other step's `finalize` has populated\nthe report collector.\n\nAt most one `OutputReport` step is allowed per pipeline (enforced in config\nvalidation); multiple would share the same collected report data.\n\nReports are keyed by their config label (from [`ReportMetadata::report_labels`],\nindexed by `report_no`); the run/input metadata the legacy `[output]` renderer\nembedded is taken from the [`ReportMetadata`] handed to the step at `init`.",
      "type": "object",
      "properties": {
        "json": {
          "description": "Emit a `{prefix}.json` report.",
          "type": "boolean"
        },
        "html": {
          "description": "Emit a `{prefix}.html` report.",
          "type": "boolean"
        }
      },
      "required": [
        "json",
        "html"
      ]
    },
    "Demultiplex": {
      "description": "Create multiple output files based on a tag",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        },
        "output_unmatched": {
          "type": "boolean"
        },
        "barcodes": {
          "description": "reference to shared barcodes section (optional for boolean tag mode)",
          "type": [
            "string",
            "null"
          ]
        },
        "in_label_contains_barcode": {
          "type": "boolean"
        },
        "tag_contains_barcode": {
          "type": "boolean"
        },
        "tag_contains_barcodes": {
          "type": "boolean"
        },
        "in_label_contains_barcodes": {
          "type": "boolean"
        }
      },
      "required": [
        "in_label",
        "output_unmatched",
        "in_label_contains_barcode"
      ]
    },
    "HammingOutput": {
      "type": "string",
      "enum": [
        "Barcode",
        "Label",
        "DNA",
        "barcodes",
        "labels",
        "barcode",
        "label",
        "dna"
      ]
    },
    "OnNoMatch": {
      "type": "string",
      "enum": [
        "Remove",
        "Empty",
        "Keep",
        "remove",
        "empty",
        "keep"
      ]
    },
    "OnTie": {
      "type": "string",
      "enum": [
        "Remove",
        "Empty",
        "Keep",
        "First",
        "FirstStrict",
        "Fail",
        "ByMajority",
        "ByEditProbability",
        "remove",
        "empty",
        "keep",
        "first",
        "firststrict",
        "fail",
        "bymajority",
        "byeditprobability"
      ]
    },
    "CountsFromReport": {
      "description": "Correct a tag (extracted region) to known barcodes",
      "type": "object",
      "properties": {
        "filename": {
          "type": "string"
        },
        "report_name": {
          "type": "string"
        },
        "tag_name": {
          "type": "string"
        }
      },
      "required": [
        "filename",
        "report_name",
        "tag_name"
      ]
    },
    "HammingCorrect": {
      "type": "object",
      "properties": {
        "in_label": {
          "description": "Input tag to correct",
          "type": "string"
        },
        "out_label": {
          "description": "Output tag to store corrected result",
          "type": "string"
        },
        "barcodes": {
          "description": "Reference to barcodes section",
          "type": "string"
        },
        "max_hamming_distance": {
          "description": "Maximum hamming distance for correction",
          "type": "integer",
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "output": {
          "$ref": "#/$defs/HammingOutput"
        },
        "on_no_match": {
          "description": "What to do when no match is found",
          "$ref": "#/$defs/OnNoMatch"
        },
        "on_tie": {
          "description": "What to do when more than one match an the same distance is found",
          "$ref": "#/$defs/OnTie"
        },
        "on_tie_min_molecules_to_start": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "on_tie_threshold": {
          "type": "number",
          "format": "double"
        },
        "on_tie_use_counts_from_report": {
          "anyOf": [
            {
              "$ref": "#/$defs/CountsFromReport"
            },
            {
              "type": "null"
            }
          ]
        },
        "name_split_character": {
          "description": "names are considered identical if they match up to the first `name_split_character`",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        },
        "by_majority_min_molecules_to_start": {
          "type": "integer",
          "format": "uint",
          "minimum": 0
        },
        "by_majority_threshold": {
          "type": "number",
          "format": "double"
        },
        "name_split_char": {
          "description": "names are considered identical if they match up to the first `name_split_character`",
          "type": [
            "integer",
            "null"
          ],
          "format": "uint8",
          "minimum": 0,
          "maximum": 255
        }
      },
      "required": [
        "in_label",
        "out_label",
        "barcodes",
        "max_hamming_distance",
        "output",
        "on_no_match",
        "on_tie",
        "on_tie_min_molecules_to_start",
        "on_tie_threshold"
      ]
    },
    "HammingExactCounter": {
      "description": "This transformation counts exact hamming matches until we've got enough counts to over to\n`HammingCorrect` in `ByMajority` mode.\n\nIt's created in `expand_transformations`",
      "type": "object",
      "properties": {
        "in_label": {
          "type": "string"
        }
      },
      "required": [
        "in_label"
      ]
    },
    "AssignByHalves": {
      "description": "Assign each query sequence to the closest entry in a named reference database.\n\nThe reference is a FASTA or FASTQ file (optionally gzip-compressed).  Every\nsequence in the file must have the same length as the query.  The step\nbuilds a Hamming-distance index at initialisation and stores the matched\nreference name as a string tag.  When no entry is within `max_hamming_distance`\nthe tag is set to `Missing`, which lets a downstream `FilterByTag` discard\nunassigned reads.",
      "type": "object",
      "properties": {
        "in_label": {
          "description": "Tag containing the query sequence (must be a String or Location tag\nwhose sequences have the same length as the reference entries).",
          "type": "string"
        },
        "out_label": {
          "description": "Output tag where the matched reference name is stored.",
          "type": "string"
        },
        "barcodes": {
          "description": "Reference to barcodes section",
          "type": "string"
        }
      },
      "required": [
        "in_label",
        "out_label",
        "barcodes"
      ]
    },
    "Benchmark": {
      "type": "object",
      "properties": {
        "enable": {
          "description": "Enable benchmark mode",
          "type": "boolean"
        },
        "molecule_count": {
          "description": "Number of molecules to process in benchmark mode",
          "type": "integer",
          "format": "uint",
          "minimum": 0
        }
      },
      "required": [
        "enable",
        "molecule_count"
      ]
    }
  }
}
