diff --git a/Cargo.lock b/Cargo.lock index 5b1bcaa..2cde33e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1057,6 +1057,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + [[package]] name = "brotli" version = "8.0.4" @@ -1165,6 +1174,15 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.4.2" @@ -2849,6 +2867,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecb" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfbf3db731928d6912bc3beda911d55b834cee3df6131ba79b337a1298a3fa9" +dependencies = [ + "cipher", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -2898,7 +2925,7 @@ checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "elide" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "elide-codec", @@ -2919,7 +2946,7 @@ dependencies = [ [[package]] name = "elide-bento" version = "0.1.0" -source = "git+https://github.com/nvisycom/bento?branch=main#70e1cba24bc39d01b1e35ecac4d58d05a376bd53" +source = "git+https://github.com/nvisycom/elide-bento?branch=main#543c2e554de73f0c5820a0d7b7cdac83e596671a" dependencies = [ "async-trait", "base64 0.23.1", @@ -2936,13 +2963,15 @@ dependencies = [ [[package]] name = "elide-codec" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "bytes", "csv", "ego-tree", "elide-core", + "elide-docx", + "elide-pdf", "hex", "hound", "image", @@ -2953,13 +2982,12 @@ dependencies = [ "sha2 0.11.0", "symphonia", "tracing", - "zip", ] [[package]] name = "elide-context" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "elide-core", @@ -2970,7 +2998,7 @@ dependencies = [ [[package]] name = "elide-core" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "blake3", @@ -2990,7 +3018,7 @@ dependencies = [ [[package]] name = "elide-detection" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "elide-core", "futures", @@ -2999,10 +3027,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "elide-docx" +version = "0.1.0" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" +dependencies = [ + "bytes", + "hipstr", + "quick-xml", + "thiserror", + "zip", +] + [[package]] name = "elide-engine" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "bytes", "elide-codec", @@ -3017,7 +3057,7 @@ dependencies = [ [[package]] name = "elide-fake" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "elide-core", @@ -3025,10 +3065,25 @@ dependencies = [ "uuid", ] +[[package]] +name = "elide-governance" +version = "0.1.0" +source = "git+https://github.com/nvisycom/elide-runtime?branch=main#1786c1aa8320af68a1e367a89f0b6e6678acd510" +dependencies = [ + "elide-core", + "elide-operator", + "hipstr", + "schemars 1.2.2", + "semver", + "serde", + "strum 0.28.0", + "uuid", +] + [[package]] name = "elide-lingua" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "elide-core", @@ -3039,7 +3094,7 @@ dependencies = [ [[package]] name = "elide-llm" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "derive_builder", @@ -3061,7 +3116,7 @@ dependencies = [ [[package]] name = "elide-ner" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "derive_builder", @@ -3075,7 +3130,7 @@ dependencies = [ [[package]] name = "elide-ocr" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "elide-core", @@ -3085,7 +3140,7 @@ dependencies = [ [[package]] name = "elide-operator" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "aes-gcm", "async-trait", @@ -3107,7 +3162,7 @@ dependencies = [ [[package]] name = "elide-pattern" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "aho-corasick", "async-trait", @@ -3124,10 +3179,42 @@ dependencies = [ "toml 1.1.4+spec-1.1.0", ] +[[package]] +name = "elide-pdf" +version = "0.1.0" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" +dependencies = [ + "bytes", + "hipstr", + "image", + "lopdf", + "thiserror", +] + +[[package]] +name = "elide-pipeline" +version = "0.1.0" +source = "git+https://github.com/nvisycom/elide-runtime?branch=main#1786c1aa8320af68a1e367a89f0b6e6678acd510" +dependencies = [ + "bytes", + "csv", + "elide", + "elide-bento", + "elide-core", + "elide-governance", + "elide-template", + "elide-wire", + "hipstr", + "schemars 1.2.2", + "serde", + "serde_json", + "uuid", +] + [[package]] name = "elide-redaction" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "elide-core", @@ -3141,13 +3228,43 @@ dependencies = [ [[package]] name = "elide-stt" version = "0.1.0" -source = "git+https://github.com/nvisycom/elide?branch=main#3facedc82c16584f00a69ce64d45126c3bcc0380" +source = "git+https://github.com/nvisycom/elide?branch=main#df3d7081263e7fe54a5deda037c1f0f8d8d418f3" dependencies = [ "async-trait", "elide-core", "uuid", ] +[[package]] +name = "elide-template" +version = "0.1.0" +source = "git+https://github.com/nvisycom/elide-runtime?branch=main#1786c1aa8320af68a1e367a89f0b6e6678acd510" +dependencies = [ + "elide-core", + "elide-governance", + "elide-operator", + "hipstr", + "jiff", + "schemars 1.2.2", + "semver", + "serde", + "uuid", +] + +[[package]] +name = "elide-wire" +version = "0.1.0" +source = "git+https://github.com/nvisycom/elide-runtime?branch=main#1786c1aa8320af68a1e367a89f0b6e6678acd510" +dependencies = [ + "bytes", + "elide-core", + "hipstr", + "jiff", + "schemars 1.2.2", + "serde", + "uuid", +] + [[package]] name = "email_address" version = "0.2.9" @@ -3724,7 +3841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" dependencies = [ "color_quant", - "weezl", + "weezl 0.1.12", ] [[package]] @@ -4380,6 +4497,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ + "block-padding", "hybrid-array", ] @@ -5475,6 +5593,33 @@ dependencies = [ "imgref", ] +[[package]] +name = "lopdf" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2ec995d822e05cabc3f06d196ee43650af3fe4fe38012cacb35e0c3d113b68" +dependencies = [ + "aes", + "bitflags 2.13.1", + "cbc", + "ecb", + "encoding_rs", + "flate2", + "getrandom 0.4.3", + "image", + "indexmap 2.14.0", + "itoa", + "log", + "md-5 0.11.0", + "nom 8.0.0", + "rand 0.10.2", + "rangemap", + "sha2 0.11.0", + "stringprep", + "thiserror", + "weezl 0.2.1", +] + [[package]] name = "lru-cache" version = "0.1.2" @@ -6054,25 +6199,6 @@ dependencies = [ "serde", ] -[[package]] -name = "nvisy-engine" -version = "0.1.0" -source = "git+https://github.com/nvisycom/runtime?branch=main#3e805a7779fb803f9baecc6535668d224f98077f" -dependencies = [ - "bytes", - "csv", - "elide", - "elide-bento", - "elide-core", - "hipstr", - "nvisy-schema", - "nvisy-template", - "schemars 1.2.2", - "serde", - "serde_json", - "uuid", -] - [[package]] name = "nvisy-inference" version = "0.1.0" @@ -6124,20 +6250,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "nvisy-policy" -version = "0.1.0" -source = "git+https://github.com/nvisycom/runtime?branch=main#3e805a7779fb803f9baecc6535668d224f98077f" -dependencies = [ - "elide-core", - "elide-operator", - "hipstr", - "schemars 1.2.2", - "serde", - "strum 0.28.0", - "uuid", -] - [[package]] name = "nvisy-postgres" version = "0.1.0" @@ -6170,21 +6282,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "nvisy-schema" -version = "0.1.0" -source = "git+https://github.com/nvisycom/runtime?branch=main#3e805a7779fb803f9baecc6535668d224f98077f" -dependencies = [ - "bytes", - "elide-core", - "hipstr", - "jiff", - "nvisy-policy", - "schemars 1.2.2", - "serde", - "uuid", -] - [[package]] name = "nvisy-server" version = "0.1.0" @@ -6206,6 +6303,7 @@ dependencies = [ "croner", "derive_more", "dotenvy", + "elide-pipeline", "futures", "hex", "hkdf", @@ -6216,7 +6314,6 @@ dependencies = [ "jsonwebtoken", "mime_guess", "nvisy-core", - "nvisy-engine", "nvisy-inference", "nvisy-nats", "nvisy-object", @@ -6245,21 +6342,6 @@ dependencies = [ "zxcvbn", ] -[[package]] -name = "nvisy-template" -version = "0.1.0" -source = "git+https://github.com/nvisycom/runtime?branch=main#3e805a7779fb803f9baecc6535668d224f98077f" -dependencies = [ - "elide-core", - "hipstr", - "jiff", - "nvisy-policy", - "schemars 1.2.2", - "semver", - "serde", - "uuid", -] - [[package]] name = "nvisy-webhook" version = "0.1.0" @@ -8376,6 +8458,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.10.9" @@ -8993,7 +9081,7 @@ dependencies = [ "flate2", "half", "quick-error", - "weezl", + "weezl 0.1.12", "zune-jpeg", ] @@ -9727,6 +9815,7 @@ dependencies = [ "getrandom 0.4.3", "js-sys", "serde_core", + "sha1_smol", "wasm-bindgen", ] @@ -9999,6 +10088,12 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "weezl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ca08e5ef825b65b056d9efbd95c8750683f0a6d0466d02e96dc2e4e360f3d2" + [[package]] name = "whoami" version = "2.1.2" diff --git a/Cargo.toml b/Cargo.toml index 8ff8ab5..64df4ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,8 +31,10 @@ documentation = "https://docs.rs/nvisy-server" # # See for more details: https://github.com/rust-lang/cargo/issues/11329 +# Elide crates +elide-pipeline = { git = "https://github.com/nvisycom/elide-runtime", branch = "main", default-features = false } + # Internal crates -nvisy-engine = { git = "https://github.com/nvisycom/runtime", branch = "main" } nvisy-core = { path = "./crates/nvisy-core", version = "0.1.0" } nvisy-nats = { path = "./crates/nvisy-nats", version = "0.1.0" } nvisy-inference = { path = "./crates/nvisy-inference", version = "0.1.0" } diff --git a/crates/nvisy-postgres/src/model/workspace_policy.rs b/crates/nvisy-postgres/src/model/workspace_policy.rs index 02fd869..33c64f4 100644 --- a/crates/nvisy-postgres/src/model/workspace_policy.rs +++ b/crates/nvisy-postgres/src/model/workspace_policy.rs @@ -10,8 +10,8 @@ use crate::types::{Handle, HasCreatedAt, HasDeletedAt, HasUpdatedAt}; /// Workspace policy representing a structured redaction governance policy. /// -/// The `definition` holds a `nvisy_schema` Policy (rules, labels, fallback, -/// retention) that the redaction engine consumes. +/// The `definition` holds an `elide-governance` `PolicyDefinition` (rules, +/// labels, fallback, retention) that the redaction engine consumes. #[derive(Debug, Clone, PartialEq, Queryable, Selectable)] #[diesel(table_name = workspace_policies)] #[diesel(check_for_backend(diesel::pg::Pg))] diff --git a/crates/nvisy-server/Cargo.toml b/crates/nvisy-server/Cargo.toml index ca166e0..50b58c6 100644 --- a/crates/nvisy-server/Cargo.toml +++ b/crates/nvisy-server/Cargo.toml @@ -31,8 +31,16 @@ default = [] cli = ["dep:clap", "dep:humantime"] [dependencies] +# Elide crates. The workspace dep disables default features, so re-enable every +# modality (all-modalities: tabular/image/audio/document) plus JSON/CSV audit +# export. elide-pipeline re-exports every engine type this crate uses. +elide-pipeline = { workspace = true, features = [ + "all-modalities", + "audit-json", + "audit-csv", +] } + # Internal crates -nvisy-engine = { workspace = true, features = ["audit-json", "audit-csv"] } nvisy-core = { workspace = true, features = ["schema"] } nvisy-nats = { workspace = true, features = [] } nvisy-inference = { workspace = true, features = ["schema"] } diff --git a/crates/nvisy-server/src/handler/catalog.rs b/crates/nvisy-server/src/handler/catalog.rs index 681bbc2..c7411ee 100644 --- a/crates/nvisy-server/src/handler/catalog.rs +++ b/crates/nvisy-server/src/handler/catalog.rs @@ -5,12 +5,12 @@ //! target) and the recognizers the engine has registered. Both are //! deployment-owned reference data, not persisted rows: labels come from the //! runtime's built-in [`LabelCatalog`], recognizers from the configured -//! [`Engine`](nvisy_engine::Engine) lineup. +//! [`Engine`](elide_pipeline::Engine) lineup. use aide::axum::ApiRouter; use aide::transform::TransformOperation; use axum::extract::State; -use nvisy_engine::entity::LabelCatalog; +use elide_pipeline::entity::LabelCatalog; use crate::extract::{AuthState, Json}; use crate::handler::response::{ErrorResponse, RecognizerCatalog}; @@ -69,7 +69,7 @@ pub fn routes() -> ApiRouter { #[cfg(test)] mod tests { - use nvisy_engine::entity::LabelCatalog; + use elide_pipeline::entity::LabelCatalog; #[test] fn builtin_labels_are_non_empty() { diff --git a/crates/nvisy-server/src/handler/error/engine_error.rs b/crates/nvisy-server/src/handler/error/engine_error.rs index 9ac4c2c..0f53dbd 100644 --- a/crates/nvisy-server/src/handler/error/engine_error.rs +++ b/crates/nvisy-server/src/handler/error/engine_error.rs @@ -1,15 +1,24 @@ //! Redaction-engine error to HTTP error conversion. //! -//! Maps `nvisy_engine::Error` onto an HTTP error. An analyze/anonymize failure -//! is a server-side processing fault, so it surfaces as an internal error with -//! the engine's own message as context. +//! Maps `elide_pipeline::Error` onto an HTTP error by its kind: a +//! `MalformedInput` is a bad document the caller supplied (a client error), +//! while every other kind — including `CapabilityUnavailable` (a codec/renderer +//! the build does not ship) — is a server-side processing fault. The engine's +//! own message travels along as context. + +use elide_pipeline::ErrorKind as EngineErrorKind; use super::http_error::{Error as HttpError, ErrorKind}; -impl<'a> From for HttpError<'a> { - fn from(error: nvisy_engine::Error) -> Self { - ErrorKind::InternalServerError - .with_message("Redaction engine failed") - .with_context(error.to_string()) +impl<'a> From for HttpError<'a> { + fn from(error: elide_pipeline::Error) -> Self { + match error.kind() { + EngineErrorKind::MalformedInput => ErrorKind::BadRequest + .with_message("Document could not be processed") + .with_context(error.to_string()), + _ => ErrorKind::InternalServerError + .with_message("Redaction engine failed") + .with_context(error.to_string()), + } } } diff --git a/crates/nvisy-server/src/handler/pipeline_audits.rs b/crates/nvisy-server/src/handler/pipeline_audits.rs index 520522b..5b38841 100644 --- a/crates/nvisy-server/src/handler/pipeline_audits.rs +++ b/crates/nvisy-server/src/handler/pipeline_audits.rs @@ -14,7 +14,7 @@ use axum::body::Body; use axum::extract::State; use axum::http::header::{CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_TYPE}; use axum::http::{HeaderMap, HeaderValue, StatusCode}; -use nvisy_engine::Audit; +use elide_pipeline::Audit; use nvisy_postgres::PgClient; use zip::write::SimpleFileOptions; diff --git a/crates/nvisy-server/src/handler/policies.rs b/crates/nvisy-server/src/handler/policies.rs index 23d977e..1e0d052 100644 --- a/crates/nvisy-server/src/handler/policies.rs +++ b/crates/nvisy-server/src/handler/policies.rs @@ -10,6 +10,7 @@ use aide::axum::ApiRouter; use aide::transform::TransformOperation; use axum::extract::State; use axum::http::StatusCode; +use elide_pipeline::policy::PolicyDefinition; use nvisy_postgres::model::{NewWorkspacePolicy, UpdateWorkspacePolicy, WorkspacePolicy}; use nvisy_postgres::query::WorkspacePolicyRepository; use nvisy_postgres::types::WithAccountRef; @@ -56,10 +57,9 @@ async fn create_policy( .authorize_workspace(&mut conn, workspace.id, Permission::ManagePolicies) .await?; - // Resolve the body (inline or a built-in template) and give it a fresh id so - // policies from one template stay independent. - let mut definition = request.body.into_definition(); - definition.id = Uuid::now_v7(); + // Resolve the body (inline or a built-in template). `into_definition` mints a + // fresh id and stamps the template origin (server-owned). + let definition = request.body.into_definition(); let display_name = request .display_name @@ -249,8 +249,17 @@ async fn update_policy( .await? .item; - let definition = match &request.definition { - Some(definition) => Some(crypto.encrypt_json(workspace.id, definition)?), + // A replaced body keeps the policy's server-owned template origin: the caller + // authored new rules, but where the policy came from is provenance the client + // cannot set or clear. Carry the stored origin forward onto the new draft. + let definition = match request.definition { + Some(draft) => { + let template = crypto + .decrypt_json::(workspace.id, &existing.definition)? + .template; + let definition = draft.into_definition(template); + Some(crypto.encrypt_json(workspace.id, &definition)?) + } None => None, }; diff --git a/crates/nvisy-server/src/handler/request/files.rs b/crates/nvisy-server/src/handler/request/files.rs index 01c97e0..8e1f744 100644 --- a/crates/nvisy-server/src/handler/request/files.rs +++ b/crates/nvisy-server/src/handler/request/files.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use std::collections::BTreeSet; use derive_more::{AsRef, Into}; -use nvisy_engine::FormatRegistry; +use elide_pipeline::FormatRegistry; use nvisy_postgres::model::UpdateWorkspaceFile as UpdateFileModel; use nvisy_postgres::types::FileFilter; use schemars::{JsonSchema, Schema, SchemaGenerator}; diff --git a/crates/nvisy-server/src/handler/request/pipeline_runs.rs b/crates/nvisy-server/src/handler/request/pipeline_runs.rs index ac0ce2f..fe52231 100644 --- a/crates/nvisy-server/src/handler/request/pipeline_runs.rs +++ b/crates/nvisy-server/src/handler/request/pipeline_runs.rs @@ -1,6 +1,6 @@ //! Pipeline run request types (detect). -use nvisy_engine::plan::ScopeParams; +use elide_pipeline::plan::ScopeParams; use nvisy_postgres::types::{PipelineRunStatus, PipelineTriggerType, RunFilter}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/crates/nvisy-server/src/handler/request/pipelines.rs b/crates/nvisy-server/src/handler/request/pipelines.rs index 7ae3116..af01363 100644 --- a/crates/nvisy-server/src/handler/request/pipelines.rs +++ b/crates/nvisy-server/src/handler/request/pipelines.rs @@ -4,7 +4,7 @@ //! creation, updates, and filtering. All request types support JSON serialization //! and validation. -use nvisy_engine::plan::ScopeParams; +use elide_pipeline::plan::ScopeParams; use nvisy_postgres::model::{NewWorkspacePipeline, UpdateWorkspacePipeline as UpdatePipelineModel}; use nvisy_postgres::types::{Handle, Json, PipelineMetadata, PipelineStatus, RetentionOverride}; use schemars::JsonSchema; diff --git a/crates/nvisy-server/src/handler/request/policies.rs b/crates/nvisy-server/src/handler/request/policies.rs index 4a2697b..fb8ce9b 100644 --- a/crates/nvisy-server/src/handler/request/policies.rs +++ b/crates/nvisy-server/src/handler/request/policies.rs @@ -1,10 +1,12 @@ //! Policy request types. -use nvisy_engine::policy::PolicyDefinition; -use nvisy_engine::template::PolicyTemplate; +use elide_pipeline::policy::redaction::ModalityRedactions; +use elide_pipeline::policy::{LabelGroup, Labels, PolicyDefinition, PolicyRule, TemplateOrigin}; +use elide_pipeline::template::PolicyTemplate; use nvisy_postgres::types::Handle; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use uuid::Uuid; use validator::Validate; /// Path parameters for policy operations. @@ -21,10 +23,60 @@ pub struct PolicyPathParams { pub policy_slug: String, } +/// A client-authored policy body: the parts of a policy definition a caller may +/// set, without the fields the server owns. +/// +/// The engine's `PolicyDefinition` also carries an `id` and a `template` origin. +/// Both are server-owned — the `id` is minted at creation and the `template` +/// records which built-in a policy was seeded from (provenance). Neither is +/// representable here, so a client cannot mint ids or forge provenance; the +/// server stamps them in [`into_definition`](PolicyDraft::into_definition). +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct PolicyDraft { + /// Human-readable name. Display-only. + pub name: String, + /// Optional description for reviewers. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Vocabulary the policy operates over: builtins picked by name plus + /// caller-authored custom label schemas. + #[serde(default, skip_serializing_if = "Labels::is_empty")] + pub labels: Labels, + /// Named clusters of labels this policy's rules may reference by name. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub groups: Vec, + /// Ordered rules. First match wins within this policy. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub rules: Vec, + /// Per-policy catch-all, fired when no rule matched. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fallback: Option, +} + +impl PolicyDraft { + /// Builds a full engine [`PolicyDefinition`] from this draft, stamping the + /// server-owned fields: a fresh `id`, and the given `template` origin + /// (`None` for a hand-authored body, the built-in's origin when seeded from + /// a template). + pub fn into_definition(self, template: Option) -> PolicyDefinition { + PolicyDefinition { + id: Uuid::now_v7(), + name: self.name.into(), + description: self.description.map(Into::into), + template, + labels: self.labels, + groups: self.groups, + rules: self.rules, + fallback: self.fallback, + } + } +} + /// Where a new policy's body comes from: exactly one source, enforced by the /// type so neither-nor-both is unrepresentable. /// -/// Tagged by `source`: `{ "source": "template", "template": "hipaa_safe_harbor" }` +/// Tagged by `source`: `{ "source": "template", "template": { ... } }` /// or `{ "source": "inline", "definition": { ... } }`. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[serde(tag = "source", rename_all = "camelCase")] @@ -32,28 +84,36 @@ pub enum PolicyBody { /// Seed the body from a built-in policy template. /// /// The template's body is copied into a normal, independently-editable - /// policy at creation time. + /// policy at creation time, tagged with the template's origin. Template { /// The built-in policy template to seed from. template: PolicyTemplate, }, /// An inline structured policy body consumed by the engine. Inline { - /// The structured policy body. + /// The client-authored policy body. /// /// Boxed to keep the enum small: an inline body is much larger than a /// template id, and most requests use a template. - definition: Box, + definition: Box, }, } impl PolicyBody { - /// Resolves the body source into a concrete policy definition: the inline - /// body as-is, or the template's body materialized from the runtime. + /// Resolves the body source into a concrete policy definition with a fresh + /// `id`, so two policies seeded from the same template stay independent. + /// + /// An inline body is hand-authored, so it carries no template origin; a + /// template body keeps the template's own origin (stamped by `build`). pub fn into_definition(self) -> PolicyDefinition { match self { - PolicyBody::Inline { definition } => *definition, - PolicyBody::Template { template } => template.build().policy, + PolicyBody::Inline { definition } => definition.into_definition(None), + PolicyBody::Template { template } => PolicyDefinition { + // `build()` bakes a stable constant id; re-mint so each created + // policy is distinct. + id: Uuid::now_v7(), + ..template.build().policy + }, } } } @@ -81,7 +141,9 @@ pub struct CreatePolicy { /// Request payload for updating an existing workspace policy. /// -/// Replacing the `definition` replaces the whole policy body. +/// Replacing the `definition` replaces the whole policy body. The policy's +/// template origin is server-owned and preserved across updates — it is not +/// settable here. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Validate)] #[serde(rename_all = "camelCase")] pub struct UpdatePolicy { @@ -92,5 +154,5 @@ pub struct UpdatePolicy { #[validate(length(max = 4096))] pub description: Option>, /// New policy body (replaces the stored definition). - pub definition: Option, + pub definition: Option, } diff --git a/crates/nvisy-server/src/handler/response/catalog.rs b/crates/nvisy-server/src/handler/response/catalog.rs index d31c0ea..339f0a2 100644 --- a/crates/nvisy-server/src/handler/response/catalog.rs +++ b/crates/nvisy-server/src/handler/response/catalog.rs @@ -1,6 +1,6 @@ //! Deployment catalog response types. -use nvisy_engine::RegisteredRecognizer; +use elide_pipeline::RegisteredRecognizer; use schemars::JsonSchema; use serde::Serialize; diff --git a/crates/nvisy-server/src/handler/response/policies.rs b/crates/nvisy-server/src/handler/response/policies.rs index b376291..ceb2aac 100644 --- a/crates/nvisy-server/src/handler/response/policies.rs +++ b/crates/nvisy-server/src/handler/response/policies.rs @@ -1,7 +1,7 @@ //! Policy response types. +use elide_pipeline::policy::PolicyDefinition; use jiff::Timestamp; -use nvisy_engine::policy::PolicyDefinition; use nvisy_postgres::model::WorkspacePolicy; use nvisy_postgres::types::Handle; use schemars::JsonSchema; diff --git a/crates/nvisy-server/src/service/detection/job.rs b/crates/nvisy-server/src/service/detection/job.rs index 429840d..d14ad50 100644 --- a/crates/nvisy-server/src/service/detection/job.rs +++ b/crates/nvisy-server/src/service/detection/job.rs @@ -1,6 +1,6 @@ //! Detection job and run-status event types. -use nvisy_engine::plan::ScopeParams; +use elide_pipeline::plan::ScopeParams; use nvisy_postgres::types::PipelineRunStatus; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/crates/nvisy-server/src/service/detection/support.rs b/crates/nvisy-server/src/service/detection/support.rs index 14050be..e6a199f 100644 --- a/crates/nvisy-server/src/service/detection/support.rs +++ b/crates/nvisy-server/src/service/detection/support.rs @@ -1,6 +1,6 @@ //! Shared detection helpers used by both the create-run handler and the worker. -use nvisy_engine::policy::PolicyDefinition; +use elide_pipeline::policy::PolicyDefinition; use nvisy_postgres::model::UpdateWorkspacePipelineRun; use nvisy_postgres::query::{ PipelineReferenceRepository, WorkspacePipelineRunRepository, WorkspacePolicyRepository, diff --git a/crates/nvisy-server/src/service/detection/worker.rs b/crates/nvisy-server/src/service/detection/worker.rs index 2fcf22f..65b5048 100644 --- a/crates/nvisy-server/src/service/detection/worker.rs +++ b/crates/nvisy-server/src/service/detection/worker.rs @@ -8,7 +8,7 @@ use std::time::Duration; -use nvisy_engine::OcrMode; +use elide_pipeline::RasterMode; use nvisy_nats::stream::DetectionStream; use nvisy_postgres::PgConn; use nvisy_postgres::model::{UpdateWorkspacePipelineRun, WorkspacePipeline, WorkspacePipelineRun}; @@ -272,7 +272,7 @@ impl DetectionWorker { let settings = workspace.settings.or_default(); let params = self.engine - .analyzer_params(&definition, job.scope.clone(), ocr_mode_of(&settings)); + .analyzer_params(&definition, job.scope.clone(), raster_mode_of(&settings)); let document = self.blob.build_document(&file, run.id).await?; @@ -340,11 +340,12 @@ enum JobOutcome { Retry, } -/// Maps a workspace's OCR policy to the engine's per-run OCR mode. -fn ocr_mode_of(settings: &WorkspaceSettings) -> OcrMode { +/// Maps a workspace's OCR policy to the engine's per-run page-rasterisation +/// mode. +fn raster_mode_of(settings: &WorkspaceSettings) -> RasterMode { match settings.ocr { - OcrPolicy::Auto => OcrMode::Auto, - OcrPolicy::Force => OcrMode::force(), - OcrPolicy::Never => OcrMode::Never, + OcrPolicy::Auto => RasterMode::Auto, + OcrPolicy::Force => RasterMode::always(), + OcrPolicy::Never => RasterMode::Never, } } diff --git a/crates/nvisy-server/src/service/engine/config.rs b/crates/nvisy-server/src/service/engine/config.rs index ee5f7f7..09ea100 100644 --- a/crates/nvisy-server/src/service/engine/config.rs +++ b/crates/nvisy-server/src/service/engine/config.rs @@ -8,7 +8,7 @@ use std::path::Path; -use nvisy_engine::provider::{ +use elide_pipeline::provider::{ LlmConfig, LlmRecognizerConfig, NerConfig, NerRecognizerConfig, OcrConfig, OcrEnricherConfig, SttConfig, SttEnricherConfig, }; diff --git a/crates/nvisy-server/src/service/engine/mod.rs b/crates/nvisy-server/src/service/engine/mod.rs index 5498b21..75d174f 100644 --- a/crates/nvisy-server/src/service/engine/mod.rs +++ b/crates/nvisy-server/src/service/engine/mod.rs @@ -10,8 +10,8 @@ use std::collections::HashSet; use std::path::PathBuf; use derive_more::Deref; -use nvisy_engine::plan::{AnalyzerParams, AnyAnnotations, ScopeParams}; -use nvisy_engine::{Engine, OcrMode}; +use elide_pipeline::plan::{AnalyzerParams, AnyAnnotations, ScopeParams}; +use elide_pipeline::{Engine, RasterMode}; use crate::Result; use crate::handler::request::PipelineDefinition; @@ -78,15 +78,15 @@ impl EngineService { /// /// Recognition is entirely engine-owned (the built-in pattern set plus the /// deployment's NER/LLM lineups always run). Scope is the request's own, - /// falling back to the pipeline default. `ocr_mode` is the workspace's OCR - /// policy (forced vs. auto). Deduplication and calibration are engine-owned - /// defaults; the label catalog is derived from the run's policies at detect - /// time. + /// falling back to the pipeline default. `raster_mode` is the workspace's + /// page-rasterisation policy (always render vs. auto). Deduplication and + /// calibration are engine-owned defaults; the label catalog is derived from + /// the run's policies at detect time. pub fn analyzer_params( &self, definition: &PipelineDefinition, request_scope: Option, - ocr_mode: OcrMode, + raster_mode: RasterMode, ) -> AnalyzerParams { let scope = request_scope .or_else(|| definition.default_scope.clone()) @@ -94,7 +94,7 @@ impl EngineService { AnalyzerParams { scope, - ocr_mode, + raster_mode, annotations: AnyAnnotations::default(), } } diff --git a/crates/nvisy-server/src/service/run_blob_store.rs b/crates/nvisy-server/src/service/run_blob_store.rs index 202a40e..1fda54c 100644 --- a/crates/nvisy-server/src/service/run_blob_store.rs +++ b/crates/nvisy-server/src/service/run_blob_store.rs @@ -11,7 +11,7 @@ use std::io::Cursor; use std::str::FromStr; use bytes::Bytes; -use nvisy_engine::{Audit, Document}; +use elide_pipeline::{Audit, Document}; use nvisy_nats::object::{AuditBucket, AuditKey, FileKey, FilesBucket}; use nvisy_postgres::PgConn; use nvisy_postgres::model::{ diff --git a/deny.toml b/deny.toml index 4e293db..fe36194 100644 --- a/deny.toml +++ b/deny.toml @@ -20,7 +20,12 @@ unmaintained = "all" # The lint level for crates that have been yanked from their source registry yanked = "deny" # A list of advisory IDs to ignore -ignore = [] +ignore = [ + # `paste` is unmaintained (no vulnerability). It is a deep transitive dep + # of `image` (image -> exr -> pulp -> paste) with no workspace-level fix; + # elide ignores the same advisory upstream. + "RUSTSEC-2024-0436", +] [licenses] # Confidence threshold for detecting a license from a license text (higher = stricter) @@ -90,7 +95,7 @@ unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories allow-git = [ - "https://github.com/nvisycom/runtime", + "https://github.com/nvisycom/elide-runtime", "https://github.com/nvisycom/elide", - "https://github.com/nvisycom/bento", + "https://github.com/nvisycom/elide-bento", ]