Diagnosing Bates Gaps and Family Breaks in a Production Volume

A production volume that fails QC with a Bates gap or a split family is one of the most common — and most alarming — failures in the Production Validation & QC stage, because both symptoms suggest the deliverable is incomplete or internally inconsistent right before it ships. A gap means a Bates number that should exist does not, hinting at a dropped page; a family break means a parent email and its attachments landed in non-contiguous Bates ranges, so a reviewer on the receiving side would see the family torn apart. Both violate the completeness and consistency guarantees a production is expected to hold, and both will be the first thing opposing counsel probes. This guide isolates the two root causes, gives a deterministic diagnostic procedure, and delivers a targeted repair that restores continuity without renumbering the whole volume.

Diagnostic Log Signatures

The QC validator emits a recognizable rejection block when continuity fails. Capture it verbatim before touching the volume — the specific document ids and the two numbers that disagree are what you reconstruct the incident from.

text
[INFO] QC volume PROD005 : 128442 documents, 431109 pages
[ERROR] QC defect bates_gap on DOC-089214: expected 214880, found 214883
[ERROR] QC defect family_split on DOC-041002: expected contiguous block of 6, found span of 9
[ERROR] QC defect bates_collision on DOC-115889: expected > 349001, found 349001
[CRITICAL] Volume PROD005 not sealed: 3 open rejections

Symptom checklist — if two or more of these hold, you are hitting a continuity failure and not a generic export error:

  • A bates_gap rejection names a document whose start number is two or more above the previous document’s end, leaving unclaimed numbers between them.
  • A family_split rejection reports a family whose Bates span is larger than its total page count, meaning a foreign document was numbered inside the family’s range.
  • A bates_collision names a document whose start number is at or below the previous end, so a citation resolves to two pages.
  • The page total in the manifest does not equal the highest end minus the lowest start plus one.
  • Re-running QC on the same manifest reproduces the identical rejection set — the defect is deterministic, not a race.

Root-Cause Breakdown

The two symptoms have distinct causes; diagnose them separately or you will renumber the volume and reintroduce the other.

  1. Non-atomic Bates allocation. A gap almost always means a number was claimed but never produced: a worker reserved a range, then the render failed and the exception path did not release or record the reservation. Because a defensible allocator persists the high-water mark before rendering, the reserved-but-unused numbers become a permanent hole. This is a feature, not a bug — a gap is detectable and safe, whereas silently reusing the number would be a collision — but the gap still has to be reconciled before shipping. Collisions, the inverse, come from local counters: two workers each incremented their own copy of the sequence instead of claiming from a single atomic source, so both produced the same number. The fix belongs in sequential Bates numbering across parallel workers.
  2. Family ordering not enforced at numbering time. A family break means the allocator handed out numbers in an order that interleaved a foreign document between a parent and its children. This happens when documents are fed to the allocator in ingestion order or hash order rather than family order, so family_id proximity is not preserved in the Bates sequence. The family metadata is correct — it comes down intact from attachment and parent-child mapping — but the production loop ignored it when claiming ranges.
  3. Manifest and image set drift. Occasionally the numbers are correct but the manifest disagrees with what was rendered: a re-run appended new rows without superseding old ones, so the same document appears twice with different ranges. This presents as a collision or a phantom gap and is really a manifest-hygiene failure, not an allocation failure.

Remediation Architecture

Fix the ordering and the reservation discipline, then reconcile the existing volume with a targeted repair rather than a full renumber — renumbering a volume that has already been cited is itself a defensibility problem.

  1. Group by family before claiming numbers. Sort the production set by family_id, then by within-family sequence (parent first, attachments in stored order), and claim Bates ranges in that order so families are contiguous by construction.
  2. Reconcile gaps deterministically. For each gap, decide between two defensible resolutions: reissue the volume’s continuity by producing a slip-sheet placeholder into the hole (documented as an intentional gap-filler), or accept the gap and record it in the production’s gap log so the receiving party is told the numbers are non-consecutive by design. Never silently renumber downstream documents to close a gap — that shifts every citation after it.
  3. Repair splits by re-sequencing only the affected families. A split family is repaired by re-claiming a fresh contiguous range for that family at the end of the volume and superseding its old rows, leaving the rest of the volume’s numbering untouched.

The diagnostic-to-repair flow below moves from the QC rejection through classification into the correct targeted repair, then a re-validation gate.

Bates continuity diagnostic and repair flow A QC rejection enters a classifier that sorts it into gap, family split, or collision. A gap routes to slip-sheet or gap-log reconciliation. A split routes to per-family re-sequencing at the end of the volume. A collision routes to atomic-allocator repair. All three paths converge on a re-validation gate that must pass before the volume is sealed. Classify the defect, then apply the targeted repair QC rejection classifier Gap → slip-sheet or documented gap log Split → re-sequence affected family only Collision → atomic fix Re-validate must pass to seal

Targeted repair implementation

The routine below groups the production set by family, claims contiguous ranges in family order, and reports any family that would have split under the old ordering. It is minimal and runnable, and it repairs by re-sequencing rather than by editing numbers in place.

python
from dataclasses import dataclass
from typing import Dict, List, Tuple


@dataclass
class Doc:
    doc_id: str
    family_id: str
    family_seq: int   # 0 = parent, 1..n = attachments in stored order
    pages: int


def sequence_in_family_order(docs: List[Doc], start: int, prefix: str,
                             width: int) -> Tuple[List[dict], int]:
    """Claim contiguous Bates ranges in family order so no family can split.
    Returns produced rows and the next free number."""
    by_family: Dict[str, List[Doc]] = {}
    for d in docs:
        by_family.setdefault(d.family_id, []).append(d)

    rows: List[dict] = []
    n = start
    # Deterministic family order: sort by the parent's id for reproducibility.
    for family_id in sorted(by_family):
        members = sorted(by_family[family_id], key=lambda d: d.family_seq)
        family_start = n
        for d in members:
            bates_start, bates_end = n, n + d.pages - 1
            rows.append({
                "doc_id": d.doc_id,
                "family_id": family_id,
                "bates_start": f"{prefix}{bates_start:0{width}d}",
                "bates_end": f"{prefix}{bates_end:0{width}d}",
            })
            n = bates_end + 1
        # Invariant: the family occupies exactly its page count, contiguously.
        assert n - family_start == sum(d.pages for d in members)
    return rows, n

Verification Checklist

Confirm the repair before re-sealing the volume:

Conclusion

Bates gaps and family breaks look like data loss but are almost always ordering and reservation defects: a gap is a claimed-but-unproduced number, a split is a family fed to the allocator out of order. Diagnose by classifying the exact rejection, repair by claiming ranges in family order and re-sequencing only the affected families, and reconcile gaps explicitly rather than by renumbering downstream citations. Defensibility is restored the moment QC re-validates clean and the rejection history shows every continuity defect was found, resolved, and re-checked before the volume shipped.

Frequently Asked Questions

Is a Bates gap always a defect I have to fix?

Not necessarily — a gap is safe as long as it is intentional and documented. Many productions ship with non-consecutive numbering by design (for example, when withheld privileged documents consume numbers that are never produced). The requirement is that the gap be recorded in the production’s gap log and disclosed, so the receiving party knows the numbering is non-consecutive on purpose. What you must never do is silently renumber downstream documents to close a gap, because that changes citations that may already be in use.

Can I just renumber the whole volume to fix a split family?

Avoid it. If any part of the volume has been cited — in a deposition, a brief, or a prior production — renumbering shifts those citations and creates a worse defensibility problem than the split. Re-sequence only the affected family into a fresh range at the end of the volume and supersede its old rows, leaving every other citation stable.

Why does producing in hash order cause family breaks?

Because hash order is effectively random with respect to family membership. A parent and its attachments have unrelated digests, so feeding documents to the Bates allocator in hash order interleaves unrelated documents between family members. Sorting by family_id (then within-family sequence) before claiming numbers is what guarantees each family is contiguous.

For background on production numbering conventions, see the EDRM Production Standards and the Sedona Conference commentary on ESI production.

Up one level: Production Validation & QC — the stage that certifies a volume’s internal consistency.