From 8f19523d702c81eeffa559b5f3ee0e542b9bd8fc Mon Sep 17 00:00:00 2001 From: forhappy Date: Sun, 23 Aug 2026 16:49:34 -0700 Subject: [PATCH 1/2] fix(ocr): complete dependency and CI integration --- .github/workflows/compass-release.yml | 1 + LICENSE-BOOST | 23 +++++++++++++++++++++++ THIRD_PARTY_NOTICES.md | 5 +++-- crates/compass-core/src/task_context.rs | 15 +++++++-------- crates/compass-output/src/workbench.rs | 5 +++++ deny.toml | 7 +++++-- scripts/package_release.sh | 1 + 7 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 LICENSE-BOOST diff --git a/.github/workflows/compass-release.yml b/.github/workflows/compass-release.yml index ef239331..90bc7a5d 100644 --- a/.github/workflows/compass-release.yml +++ b/.github/workflows/compass-release.yml @@ -111,6 +111,7 @@ jobs: "dist/verify/$name/${{ matrix.binary }}" query --help test -f "dist/verify/$name/LICENSE-MIT" test -f "dist/verify/$name/LICENSE-APACHE" + test -f "dist/verify/$name/LICENSE-BOOST" test -f "dist/verify/$name/THIRD_PARTY_NOTICES.md" ( cd dist diff --git a/LICENSE-BOOST b/LICENSE-BOOST new file mode 100644 index 00000000..36b7cd93 --- /dev/null +++ b/LICENSE-BOOST @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index ab3d1672..284cbdc8 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -5,8 +5,9 @@ Compass links OAR-OCR 0.9.2 and its `ort`/ONNX Runtime integration for optional local OCR. OAR-OCR is Apache-2.0; the Rust `ort` crates are MIT or Apache-2.0. Compass also links Hayro 0.7.1 for pure-Rust PDF rendering under MIT or -Apache-2.0. The corresponding license texts are covered by `LICENSE-MIT` and -`LICENSE-APACHE` in release bundles. +Apache-2.0. OAR-OCR's text-region geometry stack links clipper2-rust 1.1.0 +under BSL-1.0. The corresponding license texts are covered by `LICENSE-MIT`, +`LICENSE-APACHE`, and `LICENSE-BOOST` in release bundles. The separately installed `pp-ocrv6-small` and `pp-ocrv6-medium` model files come from the immutable GreatV/OAR-OCR `v0.7.0` release and originate from the diff --git a/crates/compass-core/src/task_context.rs b/crates/compass-core/src/task_context.rs index aa497795..6db66912 100644 --- a/crates/compass-core/src/task_context.rs +++ b/crates/compass-core/src/task_context.rs @@ -230,19 +230,18 @@ impl TaskContext { self.work.response_bytes ))); } - if let Some(agent) = &self.agent_knowledge { - if agent.schema != "compass.agent-knowledge/1" + if let Some(agent) = &self.agent_knowledge + && (agent.schema != "compass.agent-knowledge/1" || agent.effective_identity.as_str() != self.graph_identity || agent .assertions .len() .saturating_add(agent.challenges.len()) - > MAX_KNOWLEDGE_ITEMS as usize - { - return Err(TaskContextError::InvalidResult( - "Agent knowledge identity, schema, or record bound is invalid".to_owned(), - )); - } + > MAX_KNOWLEDGE_ITEMS as usize) + { + return Err(TaskContextError::InvalidResult( + "Agent knowledge identity, schema, or record bound is invalid".to_owned(), + )); } Ok(()) } diff --git a/crates/compass-output/src/workbench.rs b/crates/compass-output/src/workbench.rs index f3d70984..a9e93610 100644 --- a/crates/compass-output/src/workbench.rs +++ b/crates/compass-output/src/workbench.rs @@ -150,6 +150,10 @@ pub struct WorkbenchView { rename_all = "snake_case", rename_all_fields = "camelCase" )] +// Keep the established public model types and serialized contract intact. The +// effective-graph context can make history the largest variant, but boxing it +// here would be a source-breaking public API change. +#[allow(clippy::large_enum_variant)] pub enum WorkbenchViewContent { Code { model: GraphViewModel, @@ -380,6 +384,7 @@ mod tests { edges: Vec::new(), communities: Vec::new(), hyperedges: Vec::new(), + effective_graph: None, }, community_details: BTreeMap::new(), }, diff --git a/deny.toml b/deny.toml index 57090092..2e9bf958 100644 --- a/deny.toml +++ b/deny.toml @@ -7,6 +7,7 @@ yanked = "deny" ignore = [ { id = "RUSTSEC-2024-0436", reason = "Candle's GEMM stack uses paste only as a compile-time proc macro; it contributes no runtime code, and Compass will remove this exception when Candle migrates to pastey" }, { id = "RUSTSEC-2021-0127", reason = "prolly-map 0.5.0 uses serde_cbor only to persist the versioned history map; this is an unmaintained advisory with no safe upgrade, and Compass will remove the exception when prolly-map migrates to a maintained CBOR implementation" }, + { id = "RUSTSEC-2026-0192", reason = "oar-ocr 0.9.2 enables imageproc's text feature, which transitively uses unmaintained ttf-parser; no safe upgrade exists, Compass does not parse fonts through its OCR API, and this exception will be removed when OAR disables that feature or imageproc migrates" }, ] [licenses] @@ -26,9 +27,11 @@ allow = [ "Zlib", ] # prolly-map 0.5.0 uses this exact xxhash implementation for content-addressed -# history nodes. BSL-1.0 is OSI-approved; keep the exception crate/version scoped -# instead of allowing the license for every future dependency. +# history nodes, while oar-ocr-core uses clipper2-rust for text-region geometry. +# BSL-1.0 is OSI-approved; keep both exceptions crate/version scoped instead of +# allowing the license for every future dependency. exceptions = [ + { allow = ["BSL-1.0"], crate = "clipper2-rust@1.1.0" }, { allow = ["BSL-1.0"], crate = "xxhash-rust@0.8.18" }, ] diff --git a/scripts/package_release.sh b/scripts/package_release.sh index 11e8fb42..de673f81 100755 --- a/scripts/package_release.sh +++ b/scripts/package_release.sh @@ -37,6 +37,7 @@ cp "$repo_root/README.md" "$bundle/" cp "$repo_root/LICENSE" "$bundle/" cp "$repo_root/LICENSE-MIT" "$bundle/" cp "$repo_root/LICENSE-APACHE" "$bundle/" +cp "$repo_root/LICENSE-BOOST" "$bundle/" cp "$repo_root/THIRD_PARTY_NOTICES.md" "$bundle/" cp -R "$repo_root/completions" "$bundle/" From 18e0d5f13d16e6bad8579378ea17a9842a1ccf45 Mon Sep 17 00:00:00 2001 From: forhappy Date: Sun, 23 Aug 2026 17:44:13 -0700 Subject: [PATCH 2/2] fix(ocr): preserve Intel macOS builds --- CHANGELOG.md | 3 + COMPATIBILITY.md | 6 ++ crates/compass-cli/tests/document_cli.rs | 47 +++++++--- crates/compass-ocr/Cargo.toml | 4 +- crates/compass-ocr/src/engine.rs | 90 +++++++++++++------ crates/compass-ocr/src/lib.rs | 32 +++++++ crates/compass-ocr/src/models.rs | 1 + docs/design/document-processing.md | 15 ++-- .../document-ocr-qualification.md | 4 + docs/reference/commands.md | 3 + docs/reference/document-formats.md | 6 ++ 11 files changed, 169 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8acb438b..afda6813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,9 @@ cached and OCR-derived artifact coherence, bound aggregate raster work and per-tile deadlines, and serialize model installation while rejecting symlinked model artifacts and markers. + Preserve Intel macOS builds by excluding the unavailable ONNX runtime on that + target; native document processing remains available and managed OCR reports + an explicit unsupported-platform error before downloading model weights. - Hard-cut Swift, Dart, Scala, and Groovy/Gradle onto version-1 qualifying universal evidence pipelines. The bounded AST-first producer publishes diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 792b2ee4..3bb8c577 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -65,6 +65,12 @@ by source digest, schema, normalizer, rasterizer, OCR policy, preprocessing, profile manifest/model digests, and languages. An incompatible cache entry is a miss or explicit corruption error, never a fallback to flattened text. +Managed OCR is unavailable on Intel (`x86_64`) macOS because the pinned ONNX +Runtime distribution has no self-contained build for that target. Compass +therefore omits the OCR runtime dependency on Intel macOS instead of requiring +a system ONNX installation. Native document processing and `--ocr off` remain +fully available; `models install` and OCR-enabled processing fail explicitly. + The selected OCR identity is included in graph build and immutable history profiles. Native text remains authoritative; OCR is additive derived evidence with exact source owner, geometry, confidence, and model provenance. Partial diff --git a/crates/compass-cli/tests/document_cli.rs b/crates/compass-cli/tests/document_cli.rs index a92f7fa9..3d47023e 100644 --- a/crates/compass-cli/tests/document_cli.rs +++ b/crates/compass-cli/tests/document_cli.rs @@ -85,9 +85,14 @@ fn explicit_ocr_missing_profile_has_one_actionable_command() -> Result<(), Box Result<(), Box Result<(), Box> { + let directory = tempfile::tempdir()?; + let cache = directory.path().join("models"); + let output = Command::new(env!("CARGO_BIN_EXE_compass")) + .args(["models", "install", "pp-ocrv6-small"]) + .env("COMPASS_CACHE_DIR", &cache) + .output()?; + assert!(!output.status.success()); + assert!(output.stdout.is_empty()); + let stderr = String::from_utf8(output.stderr)?; + assert!(stderr.contains("managed local OCR is unavailable on Intel macOS")); + assert!(!cache.exists(), "unsupported model install created a cache"); Ok(()) } diff --git a/crates/compass-ocr/Cargo.toml b/crates/compass-ocr/Cargo.toml index 74ff84d7..80817de3 100644 --- a/crates/compass-ocr/Cargo.toml +++ b/crates/compass-ocr/Cargo.toml @@ -13,7 +13,6 @@ categories.workspace = true [dependencies] image.workspace = true -oar-ocr.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true @@ -21,5 +20,8 @@ tempfile.workspace = true thiserror.workspace = true ureq.workspace = true +[target.'cfg(not(all(target_os = "macos", target_arch = "x86_64")))'.dependencies] +oar-ocr.workspace = true + [lints] workspace = true diff --git a/crates/compass-ocr/src/engine.rs b/crates/compass-ocr/src/engine.rs index 8504e878..e00d0191 100644 --- a/crates/compass-ocr/src/engine.rs +++ b/crates/compass-ocr/src/engine.rs @@ -4,15 +4,22 @@ use std::io::Cursor; use std::sync::atomic::{AtomicBool, Ordering}; use image::{DynamicImage, ImageDecoder, ImageReader, RgbImage, imageops::FilterType}; +#[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] use oar_ocr::core::config::OrtSessionConfig; +#[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] use oar_ocr::oarocr::OAROCRBuilder; use sha2::{Digest, Sha256}; -use crate::models::{ModelProfile, verify_profile}; +use crate::models::ModelProfile; +#[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] +use crate::models::verify_profile; use crate::{ - OCR_ENGINE_MAX_SIDE, OCR_ENGINE_THREADS, OCR_MAX_OBSERVATIONS_PER_RASTER, - OCR_MAX_RASTER_LONG_EDGE, OCR_MAX_RASTER_PIXELS, OCR_SCHEMA, OCR_TILE_OVERLAP, OcrError, - OcrObservation, OcrPoint, OcrRequest, OcrResponse, + OCR_ENGINE_MAX_SIDE, OCR_MAX_RASTER_LONG_EDGE, OCR_MAX_RASTER_PIXELS, OCR_TILE_OVERLAP, + OcrError, OcrRequest, OcrResponse, +}; +#[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] +use crate::{ + OCR_ENGINE_THREADS, OCR_MAX_OBSERVATIONS_PER_RASTER, OCR_SCHEMA, OcrObservation, OcrPoint, }; #[derive(Clone, Debug)] @@ -53,35 +60,40 @@ pub trait OcrEngine { } pub struct ManagedOarEngine { + #[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] runtime: oar_ocr::oarocr::OAROCR, profile: crate::OcrProfileIdentity, } impl ManagedOarEngine { pub fn load(profile: ModelProfile) -> Result { - let files = verify_profile(profile)?; - let session = OrtSessionConfig::default() - .with_intra_threads(OCR_ENGINE_THREADS) - .with_inter_threads(OCR_ENGINE_THREADS); - let runtime = OAROCRBuilder::new(&files.detector, &files.recognizer, &files.dictionary) - .ort_session(session) - .image_batch_size(1) - .region_batch_size(4) - .build() - .map_err(|error| OcrError::EngineUnavailable(error.to_string()))?; - Ok(Self { - runtime, - profile: files.identity, - }) - } -} - -impl OcrEngine for ManagedOarEngine { - fn identity(&self) -> &crate::OcrProfileIdentity { - &self.profile + crate::ensure_managed_runtime_available()?; + #[cfg(all(target_os = "macos", target_arch = "x86_64"))] + { + let _ = profile; + Err(crate::managed_runtime_unavailable_error()) + } + #[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] + { + let files = verify_profile(profile)?; + let session = OrtSessionConfig::default() + .with_intra_threads(OCR_ENGINE_THREADS) + .with_inter_threads(OCR_ENGINE_THREADS); + let runtime = OAROCRBuilder::new(&files.detector, &files.recognizer, &files.dictionary) + .ort_session(session) + .image_batch_size(1) + .region_batch_size(4) + .build() + .map_err(|error| OcrError::EngineUnavailable(error.to_string()))?; + Ok(Self { + runtime, + profile: files.identity, + }) + } } - fn recognize( + #[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] + fn recognize_with_runtime( &self, request: &OcrRequest, raster: &PreparedRaster, @@ -166,6 +178,28 @@ impl OcrEngine for ManagedOarEngine { response.validate_for(request)?; Ok(response) } +} + +impl OcrEngine for ManagedOarEngine { + fn identity(&self) -> &crate::OcrProfileIdentity { + &self.profile + } + + fn recognize( + &self, + request: &OcrRequest, + raster: &PreparedRaster, + ) -> Result { + #[cfg(all(target_os = "macos", target_arch = "x86_64"))] + { + let _ = (request, raster); + Err(crate::managed_runtime_unavailable_error()) + } + #[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] + { + self.recognize_with_runtime(request, raster) + } + } fn recognize_cancellable( &self, @@ -327,6 +361,7 @@ pub fn prepare_raster_cancellable( }) } +#[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] fn geometry_key(points: &[oar_ocr::processors::Point]) -> (u32, u32) { let min_y = points .iter() @@ -341,6 +376,7 @@ fn geometry_key(points: &[oar_ocr::processors::Point]) -> (u32, u32) { (sortable_float(min_y), sortable_float(min_x)) } +#[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] fn sortable_float(value: f32) -> u32 { if !value.is_finite() || value <= 0.0 { 0 @@ -349,6 +385,10 @@ fn sortable_float(value: f32) -> u32 { } } +#[cfg(any( + not(all(target_os = "macos", target_arch = "x86_64")), + test +))] fn quantize_coordinate(value: f32, bound: u32) -> Result { if !value.is_finite() || value < 0.0 || value > bound as f32 || bound == 0 { return Err(OcrError::InvalidOutput( diff --git a/crates/compass-ocr/src/lib.rs b/crates/compass-ocr/src/lib.rs index 99cd8f63..b0ca9346 100644 --- a/crates/compass-ocr/src/lib.rs +++ b/crates/compass-ocr/src/lib.rs @@ -35,6 +35,30 @@ pub const OCR_MAX_TEXT_CHARS_PER_DOCUMENT: usize = 5_000_000; pub const OCR_MAX_LANGUAGE_HINTS: usize = 32; pub const OCR_MAX_PROFILE_FIELD_BYTES: usize = 256; +fn managed_runtime_supported_for(target_os: &str, target_arch: &str) -> bool { + target_os != "macos" || target_arch != "x86_64" +} + +#[must_use] +pub fn managed_runtime_available() -> bool { + managed_runtime_supported_for(std::env::consts::OS, std::env::consts::ARCH) +} + +pub(crate) fn ensure_managed_runtime_available() -> Result<(), OcrError> { + if managed_runtime_available() { + Ok(()) + } else { + Err(managed_runtime_unavailable_error()) + } +} + +pub(crate) fn managed_runtime_unavailable_error() -> OcrError { + OcrError::EngineUnavailable( + "managed local OCR is unavailable on Intel macOS because the pinned ONNX Runtime does not provide a self-contained x86_64 macOS build; native PDF, DOCX, PPTX, and XLSX processing remains available with OCR off" + .to_owned(), + ) +} + pub fn normalize_language_hints(hints: &[String]) -> Result, OcrError> { if hints.len() > OCR_MAX_LANGUAGE_HINTS { return Err(OcrError::InvalidRequest( @@ -538,4 +562,12 @@ mod tests { assert!(validate_dimensions(6_000, 4_001).is_err()); assert!(validate_dimensions(6_001, 1).is_err()); } + + #[test] + fn managed_runtime_support_matrix_excludes_intel_macos() { + assert!(!managed_runtime_supported_for("macos", "x86_64")); + assert!(managed_runtime_supported_for("macos", "aarch64")); + assert!(managed_runtime_supported_for("linux", "x86_64")); + assert!(managed_runtime_supported_for("windows", "x86_64")); + } } diff --git a/crates/compass-ocr/src/models.rs b/crates/compass-ocr/src/models.rs index bc8cb00b..534ccfa7 100644 --- a/crates/compass-ocr/src/models.rs +++ b/crates/compass-ocr/src/models.rs @@ -527,6 +527,7 @@ fn expected_verified_marker(profile: ModelProfile) -> VerifiedProfileMarker { } pub fn install_profile(profile: ModelProfile) -> Result { + crate::ensure_managed_runtime_available()?; ModelCache::from_environment()?.install(profile, &HttpsArtifactFetcher::default()) } diff --git a/docs/design/document-processing.md b/docs/design/document-processing.md index dea86e7f..1660630f 100644 --- a/docs/design/document-processing.md +++ b/docs/design/document-processing.md @@ -206,12 +206,15 @@ complete cache entry. The same prepared artifact feeds structural publication and gap-free Unicode-safe semantic slices. The semantic layer does not load an OCR engine or maintain a second document cache. -The PP-OCRv6 runtime is compiled with Compass. Users install no Python, -Tesseract, office suite, Poppler, Java, or system ONNX package. Model weights -are deliberately separate: `compass models install pp-ocrv6-small` is the only -download path and validates a fixed allowlisted HTTPS source, declared size, -SHA-256, and atomic verified marker. Inspection and extraction never download -or prompt. +On supported targets, the PP-OCRv6 runtime is compiled with Compass. Users +install no Python, Tesseract, office suite, Poppler, Java, or system ONNX +package. Intel (`x86_64`) macOS is excluded because the pinned ONNX Runtime has +no self-contained distribution for that target; native document processing +continues to work, while model installation and OCR-enabled processing fail +before downloading. Model weights are deliberately separate: `compass models +install pp-ocrv6-small` is the only download path on supported targets and +validates a fixed allowlisted HTTPS source, declared size, SHA-256, and atomic +verified marker. Inspection and extraction never download or prompt. The production engine identity is OAR-OCR 0.9.2 with its in-process ONNX backend. The model source is the immutable GreatV/OAR-OCR `v0.7.0` GitHub diff --git a/docs/implementation/document-ocr-qualification.md b/docs/implementation/document-ocr-qualification.md index 39dae6bf..a8367da8 100644 --- a/docs/implementation/document-ocr-qualification.md +++ b/docs/implementation/document-ocr-qualification.md @@ -36,6 +36,10 @@ Installation is the only network step. The gate itself performs no download. It verifies the immutable OAR-OCR 0.9.2 / `v0.7.0` profile, executes inference, validates geometry and profile identity, and enforces at most 5% CER on the deterministic clean-English smoke raster. The smoke raster is CC0 synthetic. +Run this installed-model gate only on managed-runtime targets. Intel +(`x86_64`) macOS intentionally supports the offline fixture gate and native +document processing but not managed OCR because the pinned ONNX Runtime has no +self-contained distribution for that target. The v1 manifest intentionally does not claim photographed pages, handwriting, table reconstruction, or any script/platform absent from measured fixtures. diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 234cfccd..23bea622 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -151,6 +151,9 @@ PDF, DOCX, PPTX, and XLSX processing requires no additional installation. `models install` is the only command here that uses the network. It downloads only pinned artifacts from the Compass allowlist, validates size and SHA-256, and publishes an atomic verification marker. `list` and `verify` are offline. +On Intel (`x86_64`) macOS, managed OCR is unavailable because the pinned ONNX +runtime has no self-contained distribution; `models install` fails before any +download, while native document processing and `--ocr off` remain available. `update`, `extract`, and watch rebuilds may succeed with a warning that Compass published a partial graph. The warning reports exact omitted node, omitted diff --git a/docs/reference/document-formats.md b/docs/reference/document-formats.md index ce96e493..182941a1 100644 --- a/docs/reference/document-formats.md +++ b/docs/reference/document-formats.md @@ -120,6 +120,12 @@ require a verified local profile and never download implicitly. Use inspection uses `compass.document.inspect/1` and includes the policy, artifact, limits, diagnostics, visual coverage, and exact OCR profile identity. +Managed OCR is unavailable on Intel (`x86_64`) macOS because its pinned ONNX +runtime has no self-contained distribution for that target. Native extraction +and `--ocr off` remain available without additional installation. Compass +rejects model installation and OCR-enabled processing there before any model +download. + Document and cache files are capped while streaming, so size checks still hold if an input changes during a read. PDF rasterization reserves aggregate pixels before rendering each page, and tiled recognition checks the document deadline