Commit graph

20 commits

Author SHA1 Message Date
jedarden
db7fcf0097 feat(pdftract-4xu46): implement grep subcommand structure with clap parsing
Add pdftract grep subcommand with ripgrep-style flag compatibility.
Implements all flags from the plan options table with proper defaults:
- Literal match mode by default (-F style)
- -E for full regex mode
- -i for case-insensitive search
- -w for word boundaries
- -v for invert match
- -l, -c for output modes
- -j for thread control
- --ocr, --json, --highlight DIR
- --progress/--no-progress/--progress-json
- Feature-gated behind 'grep' feature flag

Unit tests cover all flag combinations and edge cases.
Stub implementation exits with code 2 pending 7.8.2-7.8.10.

Closes: pdftract-4xu46
2026-05-24 05:49:15 -04:00
jedarden
66b3eff9cb feat(pdftract-jmh6w): implement rayon+tokio concurrency bridge
- Add comprehensive concurrency model documentation to serve.rs rustdoc
- Add long_about to Serve CLI command documenting tokio+rayon architecture
- Improve JoinError handling with InternalPanic error code for task panics
- Add test_concurrent_requests_parallel verifying 8 concurrent requests complete in parallel
- Add test_error_into_response and test_cache_status_conversions unit tests

The spawn_blocking pattern was already in place; this commit adds:
1. Documentation of the concurrency model in rustdoc and CLI help
2. Proper panic detection via JoinError::is_panic()
3. Error code INTERNAL_PANIC for panicking tasks
4. Integration test proving concurrent request parallelism

Closes: pdftract-jmh6w
2026-05-24 05:23:20 -04:00
jedarden
0dcae8766e feat(pdftract-kdp6): implement profile loader secret key hardening
Add PROFILE_SECRETS_FORBIDDEN diagnostic and enhanced profile validation
to prevent accidental publication of credentials in profile YAML files.

Changes:
- Add DiagCode::ProfileSecretsForbidden to diagnostics catalog
- Create pdftract-core/src/profiles/ module with loader.rs
- Implement separator-tolerant key matching (api_key/apiKey/api-key/api.key)
- Expand forbidden keys from 7 to 17 entries
- Add line number detection for error reporting
- Update ProfilePathCheck to use enhanced validation

Closes: pdftract-kdp6

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 04:41:04 -04:00
jedarden
585d861efc test(pdftract-sy8x): implement lexer proptest harness and curated corpus
Add property-based testing infrastructure for the lexer module with 6+
property tests covering INV-8 (no panic), string/hex roundtrips, name
length bounds, and position monotonicity. Create 8 curated fixture files
with golden token outputs for critical edge cases including EC-01 empty
file test and whitespace-only inputs.

Changes:
- Add prop_string_roundtrip to tests/proptest/lexer.rs
- Create tests/lexer/fixtures/ with 8 fixtures + .tokens.txt golden files
- Add gen_lexer_golden.rs binary for regenerating golden outputs
- Fix missing ObjRef import in marked_content_operators.rs

Acceptance criteria:
- cargo test --features proptest -p pdftract-core: 105 lexer tests pass
- tests/lexer/fixtures/ contains 8 fixtures with .tokens.txt outputs
- EC-01 empty file test: 0-byte input -> Token::Eof, no panic
- Whitespace-only file test passes
- INV-8 verified by prop_lexer_never_panics

Closes: pdftract-sy8x
2026-05-24 02:36:37 -04:00
jedarden
d1dc2280f1 feat(pdftract-27n3): implement border padding, pipeline orchestration, and fixtures
Implement step 5 (white-border padding: 10 px on all sides), wire all
preprocessing steps into the final preprocess(input, ImageSource) ->
GrayImage entry point, and curate fixtures for the three image-source
paths (PhysicalScan / DigitalOrigin / Jbig2).

Changes:
- Add add_border_padding() function: creates (width+20) x (height+20)
  image with 10px white border on all sides
- Add preprocess() pipeline orchestrator: applies deskew, contrast
  normalization, binarization, denoising, and padding in correct order
- Skip contrast, binarization, and denoising for JBIG2 images
- Generate test fixtures for skewed_2deg, uneven_lighting, clean_digital,
  and jbig2_scan scenarios
- Add integration tests for all critical test scenarios
- Add A4-page benchmarks targeting < 500ms for physical/digital, < 200ms
  for JBIG2

Refs:
- Plan section: Phase 5.3 step 5 (line 1878) + critical tests (lines 1882-1885)
- Bead: pdftract-27n3
- Note: notes/pdftract-27n3.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 21:55:11 -04:00
jedarden
96f71e9b52 feat(pdftract-1u80): add cargo binstall metadata and installation docs
Add [package.metadata.binstall] to crates/pdftract-cli/Cargo.toml to enable
cargo binstall to download pre-built binaries from GitHub Releases instead
of compiling from source. Also add comprehensive Installation section to
README.md documenting cargo binstall as the recommended install method.

Bead: pdftract-1u80

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 21:23:17 -04:00
jedarden
50946fc98c feat(pdftract-4my): implement serve mode integration for full-render feature
This commit completes Phase 5.2.2 by integrating the pdfium-render path
into serve mode with runtime validation and feature propagation.

Changes:
- Propagate ocr and full-render features from CLI to pdftract-core
- Add full_render parameter to serve mode ExtractParams
- Implement runtime validation in build_options():
  * Returns BadRequest if full_render requested but PDFium unavailable
  * Falls back to direct compositing if feature not compiled
- Update all three serve handlers to handle Result from build_options()

Acceptance Criteria:
 cargo build --features ocr,serve,full-render succeeds
 cargo build --features ocr,serve (no full-render) succeeds
 Runtime fallback: full_render=true with feature absent uses direct path

Notes:
- Binary size CI gate (140 MB) requires separate CI infrastructure
- Soft-mask regression tests require separate fixture work

Refs: pdftract-4my
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 16:28:08 -04:00
jedarden
c2be1da5ce docs(pdftract-1w5u1): add verification note for doctor output formats
Verified all three output formats (colored table, JSON, --features)
work correctly. No code changes required - implementation was
already complete in output/ module.

Acceptance criteria:
- PASS: Default TTY colored table with summary
- PASS: Non-TTY plain text (no ANSI codes when piped)
- PASS: --json output parses correctly with jq
- PASS: --features lists compiled features, exit 0
- PASS: --no-color forces plain text
- PASS: 80-column width compliance
- PASS: N/A rows excluded from human, included in JSON

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 07:24:02 -04:00
jedarden
3155510a5e feat(pdftract-4q8cq): implement 14 environment checks for pdftract doctor
Implemented all 14 environment checks as specified in the bead description:
- pdftract binary: version + git-sha + compiled features
- tesseract install: version check (major >= 5 OK, == 4 WARN, <= 3 FAIL)
- tesseract languages: eng + requested langs present
- leptonica install: pkg-config check >= 1.79
- libtiff: pkg-config check with ldconfig fallback
- libopenjp2: pkg-config check with ldconfig fallback
- pdfium native lib: runtime detection >= 6555
- network reachability: HEAD example.com 5s timeout
- cache directory: writable + 1 GiB free + layout version
- profile search path: YAML parse + PROFILE_SECRETS_FORBIDDEN
- ulimit -n: getrlimit check >= 1024
- available RAM: /proc/meminfo or sysctl
- system locale: UTF-8 check
- temp dir writable: TMPDIR + 100 MiB free

All checks feature-gated appropriately. Panic-safe via run_check_safe().
CLI output layer integrated with --json and --features flags.

Acceptance criteria:
-  Unit tests for OK/WARN/FAIL paths in each check
-  Runtime < 6s (network: 5s, others: <100ms)
-  Panic catching via catch_unwind
-  Feature-gated checks return NotApplicable
-  pkg-config fallback to ldconfig
-  Profile secret detection with PROFILE_SECRETS_FORBIDDEN

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-05-23 07:05:49 -04:00
jedarden
8abf01cea3 feat(pdftract-4q8cq): implement 14 environment checks for pdftract doctor
Implement all 14 environment checks for the `pdftract doctor` subcommand.
Each check returns a CheckResult with status (OK/WARN/FAIL/NotApplicable)
and a human-readable detail message.

Checks implemented:
- pdftract binary (version, git SHA, compiled features)
- tesseract install (version check: >=5 OK, ==4 WARN, <=3 FAIL)
- tesseract languages (eng + requested langs present)
- leptonica install (>=1.79 OK, older WARN, not found FAIL)
- libtiff (pkg-config check with ldconfig fallback)
- libopenjp2 (pkg-config check with ldconfig fallback)
- pdfium native lib (version >=6555 OK, older WARN, not found FAIL)
- network reachability (HEAD example.com with 5s timeout)
- cache directory (writable, free space >=1 GiB, layout version)
- profile search path (YAML parse, PROFILE_SECRETS_FORBIDDEN detection)
- ulimit -n (>=1024 OK, 512-1024 WARN, <512 FAIL)
- available RAM (>=256 MiB OK, 128-256 WARN, <128 FAIL)
- system locale (UTF-8 OK, non-UTF-8 WARN, unset FAIL)
- temp dir writable (writable + free space >=100 MiB)

Core module with Check trait, CheckResult, CheckStatus, DoctorCtx,
DoctorFeatures, and panic-safe run_check_safe wrapper.

Build script injects GIT_SHA and COMPILED_FEATURES at compile time.

All checks feature-gated appropriately (ocr, full-render, remote, profiles).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-05-23 06:47:07 -04:00
jedarden
e2c1e2817b feat(pdftract-2i6rt): implement cache CLI subcommand and HTTP integration
This commit implements Phase 6.9.6: surfacing the cache as user-visible
CLI and HTTP affordances.

## Changes

- Add `pdftract cache` subcommand with stats/clear/purge actions
  - `stats DIR`: show entry count, size, hit ratio, age distribution
  - `stats DIR --json`: emit JSON with same fields
  - `clear DIR`: delete all entries (preserves index.json/sentinel)
  - `purge DIR --older-than 30d`: delete entries older than duration
  - `purge DIR --version '<1.0.0'`: version constraint purge (stub)

- Add global flags to extract-style subcommands
  - `--cache-dir DIR`: enable cache at directory
  - `--cache-size SIZE`: set LRU size limit (default 1 GiB)
  - `--no-cache`: disable cache for this call

- Add `X-Pdftract-Cache: hit|miss|skipped` HTTP header on /extract endpoints
  - Set in response headers before body streaming

- Add JSON metadata fields
  - `metadata.cache_status`: "hit" | "miss" | "skipped"
  - `metadata.cache_age_seconds`: integer seconds (present only on hit)

## Acceptance Criteria

-  pdftract cache stats on empty dir: "Entries: 0"
-  pdftract cache stats on populated dir: correct counts and ratios
-  pdftract cache clear -y: deletes entries, preserves index/sentinel
-  pdftract cache purge --older-than: deletes old entries
-  extract --cache-dir: metadata.cache_status populated
-  extract second run: cache_status "hit" with age
-  extract --no-cache: cache_status "skipped"
-  HTTP serve: X-Pdftract-Cache header present
-  --cache-size parsing: 4GiB → 4 * 1024^3 bytes

## Modules

- crates/pdftract-cli/src/cache_cmd.rs: subcommand implementation
- crates/pdftract-cli/src/serve.rs: HTTP handler integration
- crates/pdftract-cli/src/main.rs: CLI flag definitions
- crates/pdftract-core/src/cache/mod.rs: extract_with_cache() integration
- crates/pdftract-core/src/extract.rs: cache_status metadata fields

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 06:33:43 -04:00
jedarden
210c40de8c feat(pdftract-mcp): add MCP server implementation changes
Changes from Phase 6.7 child beads that were not committed earlier:

- Add subtle dependency for constant-time token comparison
- Add root directory for path-traversal protection in HTTP+SSE transport
- Update MCP server state to support --root flag
- Minor fixes and improvements across MCP modules

These changes support the 7 closed child beads:
- pdftract-5xq16: JSON-RPC 2.0 framing layer
- pdftract-67tm8: stdio transport
- pdftract-g0ro2: HTTP+SSE transport
- pdftract-24kut: transport mutual exclusion enforcement
- pdftract-1rami: tool catalog (10 tools)
- pdftract-6696g: path-traversal protection
- pdftract-zltqd: bearer-token auth

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 03:09:56 -04:00
jedarden
7833d8c514 feat(pdftract-1rami): implement MCP tool catalog with 10 tools
Implement the MCP tool catalog for pdftract with all 10 tools wired to
the extraction surface via the MCP protocol. The tool registry provides
typed argument schemas (JSON Schema via schemars), structured error
mapping (Rust errors → JSON-RPC error codes), and per-invocation
observability logging.

- Tool registry with Tool trait and 10 tool implementations
- JSON Schema input schemas for all tools (draft-07 compliant)
- Error code mapping: -32000 NOT_YET_IMPLEMENTED, -32001 PDF_ENCRYPTED,
  -32002 IO_ERROR, -32003 PATH_INVALID
- Observability logging: structured stderr log line per tools/call
- Integration tests: 10/11 pass (1 ignored for encrypted fixture)
- Registry unit tests: 23/23 pass

Tools implemented:
- extract, extract_text, extract_markdown (stubs pending Phase 6)
- search (stub pending Phase 6)
- get_metadata, hash (fully implemented, fast paths)
- get_table, get_form_fields, get_attachments, classify (stubs return
  NOT_YET_IMPLEMENTED per spec)

Acceptance criteria: 8/8 PASS (2 WARN for Phase 6 stubs)

Refs: pdftract-1rami
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-05-23 02:12:41 -04:00
jedarden
539627795b feat(pdftract-g0ro2): implement MCP HTTP+SSE transport with integration tests
Implements the HTTP+SSE transport for the MCP server per bead pdftract-g0ro2.
All acceptance criteria PASS.

Routes:
- POST /: JSON-RPC requests (single or batch)
- GET /sse: Server-Sent Events for notifications
- GET /health: Health check (auth-exempt)

Key features:
- Reuses axum/tokio/tower-http from Phase 6.4 (no new deps)
- Bearer token auth (from sibling bead 6.7.7)
- Request body limit (256 MB default, configurable via --max-upload-mb)
- SSE keepalive every 30 seconds
- Broadcast channel for fan-out notifications
- Backpressure handling (drops lagged clients with WARN log)
- 100-client SSE limit (MAX_SSE_CLIENTS)
- Custom 413 Payload Too Large JSON response
- Batch request support per JSON-RPC 2.0 spec

All 10 integration tests pass:
- test_post_tools_list: POST / returns tool catalog
- test_get_sse_stream: GET /sse opens SSE stream with keepalive
- test_50_concurrent_clients: 50 concurrent clients succeed
- test_health_during_load: GET /health returns 200 under load
- test_post_batch_request: Batch requests return batch responses
- test_post_payload_too_large: POST / over limit returns 413 with JSON body
- test_auth_required_for_non_loopback: Bearer auth returns 401 with WWW-Authenticate
- test_post_single_request_returns_single_response: Single request returns single response
- test_unknown_method: Unknown method returns method_not_found error
- test_get_health: GET /health returns 200 with version info

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 00:35:14 -04:00
jedarden
c4ff5194dd feat(pdftract-67tm8): implement MCP stdio transport with integration tests
Implements the stdio transport for the MCP server, enabling communication
with local agents (Claude Desktop, Claude Code, Continue, Cursor) over
standard input/output with Content-Length framing.

Core features:
- LSP-style Content-Length framing with \r\n terminators
- JSON-RPC 2.0 message parsing and serialization
- INV-9 compliance: stdout contains only JSON-RPC frames
- Panic hook redirects panics to stderr
- SIGTERM handler for graceful shutdown
- Parse errors return -32700 with id: null, then continue

Acceptance criteria:
-  Piping tools/list with framing produces expected response < 50ms
-  EOF on stdin → clean exit within 100ms
-  Malformed JSON → -32700 error, subsequent requests work
-  No println!/log output to stdout (INV-9 enforced)
-  Panics go to stderr, no partial JSON on stdout
-  SIGTERM → exit 0, SIGINT → immediate non-zero exit

Tests added:
- crates/pdftract-cli/tests/mcp-stdio.rs (8 integration tests, all pass)
- All 49 existing unit tests continue to pass

Refs: pdftract-67tm8, plan Phase 6.7.2
2026-05-23 00:16:42 -04:00
jedarden
6a35bdd869 feat(pdftract-29z7b): implement unified diagnostic system + CLI commands
- Added `cmd_explain_diagnostic` function to CLI for detailed diagnostic code explanation
- Added `--list-diagnostics` and `--explain-diagnostic <code>` CLI commands
- Verified all Phase 1.1-1.5 modules use unified DiagCode (lexer, parser, xref, stream, catalog, outline, pages)
- DIAGNOSTIC_CATALOG provides metadata for all 61 diagnostic codes
- Diagnostic struct size: 56 bytes (within 48-64 target range)
- emit! macro provides ergonomic diagnostic emission
- INV-8 maintained: no panics in error paths

All diagnostic codes follow naming convention:
- STRUCT_*: PDF structure errors
- STREAM_*: Stream decoder errors
- XREF_*: Cross-reference table errors
- ENCRYPTION_*: Encryption-related errors
- OCR_*: OCR pipeline errors
- REMOTE_*: Remote source errors
- PAGE_*: Page-level errors
- FONT_*: Font pipeline errors
- GSTATE_*: Graphics state errors
- LAYOUT_*: Layout and reading order errors
- MCP_*: MCP server errors
- CACHE_*: Cache errors

References: Phase 1.6 (error recovery), INV-8, Phase 0.4 (clippy enforces doc comments)
2026-05-22 22:38:31 -04:00
jedarden
1959ff2446 feat(pdftract-3uu6v): implement LZWDecode with /EarlyChange parameter
- Add LZWDecoder filter using lzw crate v0.10
- Support /EarlyChange parameter (default 1, late 0)
  - Early change (1): Adobe/TIFF variant, code size increases BEFORE
  - Late change (0): GIF variant, code size increases AFTER
- Full predictor support (TIFF predictor 2, PNG predictors 10-15)
- Bomb limit protection with partial bytes on exceed
- INV-8 maintained: partial bytes returned on decode errors
- 23 tests pass (19 unit tests + 4 proptests)
- Fixtures generated using lzw crate for verification

Acceptance criteria:
- Critical test /EarlyChange=0 byte-perfect: PASS
- LZWDecode without /DecodeParms defaults: PASS
- LZWDecode + /Predictor 12: PASS
- Truncated stream partial bytes: PASS
- Bomb limit honored: PASS
- proptest no panic: PASS
- INV-8 maintained: PASS

Refs: Plan Phase 1.5 line 1142, PDF spec 7.4.4

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-05-22 22:38:31 -04:00
jedarden
9aa26a449e docs(pdftract-49f8): establish Cargo.lock policy and documentation
This commit implements the Cargo.lock policy for reproducible builds
across all workspace members (pdftract-core, pdftract-cli, pdftract-py).

Changes:
- Add CONTRIBUTING.md with lockfile-update workflow documentation
- Add .renovaterc.json for weekly lockfile-only PRs (human-gated)
- Add crates/pdftract-core/README.md with rationale for checked-in lockfiles
- Add notes/pdftract-49f8.md with verification note

The Argo workflow updates (pdftract-ci.yaml) are committed separately
in the declarative-config repo.

Acceptance criteria:
- PASS: Cargo.lock tracked by git, not in .gitignore
- PASS: Argo workflow templates document --locked/--frozen requirements
- WARN: Enforcement to be completed when placeholder templates are implemented
- WARN: Binary reproducibility verification deferred to pdftract-build-binaries implementation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 18:13:14 -04:00
jedarden
660a9401ef feat(pdftract-59zz): implement MCP bearer token ingress channels and TH-03 enforcement
Implements secure MCP bearer-token ingress channels and TH-03 startup abort
enforcement per plan lines 874, 915-921, 922-924.

## Changes
- Add `--auth-token-file PATH` flag (RECOMMENDED channel)
- Add `PDFTRACT_MCP_TOKEN` env var support
- Reject `--auth-token VALUE` unless `PDFTRACT_INSECURE_CLI_TOKEN=1`
- Enforce TH-03: require token for non-loopback bind addresses (exit 78)
- Loopback exemption for 127.0.0.0/8 and ::1/128

## Files
- crates/pdftract-cli/src/mcp/auth.rs: Token resolution with priority order
- crates/pdftract-cli/src/mcp/bind.rs: TH-03 bind security check
- crates/pdftract-cli/src/mcp/server.rs: MCP server entry point
- crates/pdftract-cli/src/mcp/mod.rs: Module exports
- crates/pdftract-cli/src/main.rs: CLI arguments
- crates/pdftract-cli/Cargo.toml: Add secrecy, tempfile dependencies

## Acceptance Criteria
-  --auth-token-file PATH flag implemented
-  PDFTRACT_MCP_TOKEN env var resolved
-  --auth-token VALUE rejected (exit 64) unless PDFTRACT_INSECURE_CLI_TOKEN=1
-  mcp --bind ADDR with non-loopback ADDR and no token: aborts with exit 78
-  mcp --bind ADDR with loopback ADDR and no token: succeeds
-  mcp --bind ADDR with token: succeeds regardless of address
- ⏸️ Inspector token: Phase 7.9 (not yet implemented)
- ⏸️ TH-03 test: separate bead

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 02:47:54 -04:00
jedarden
857f928732 feat(pdftract-5omc): implement SDK conformance test runner pattern
Implement the conformance test runner pattern that every SDK will
implement to validate against the shared test suite.

- Rust reference implementation (crates/pdftract-core/tests/conformance.rs)
  * Full test suite loader and executor
  * Comparison engine with min/max, string constraints, tolerances
  * Skip logic for unsupported features and schema versions
  * Report generation in JSON format

- CLI compare subcommand (crates/pdftract-cli/src/main.rs)
  * pdftract compare - Compare actual vs expected with tolerances
  * Cross-language comparison tool to avoid reimplementations

- Documentation (docs/conformance/sdk-contract.md)
  * Complete pattern specification with pseudocode
  * Per-language runner locations
  * CI integration requirements

- Python reference stub (tests/python-conformance/test_conformance.py)
  * Full pytest-based implementation following the pattern

Closes: pdftract-5omc
2026-05-18 01:22:23 -04:00