#![deny(unsafe_code)]
#![deny(missing_docs)]
pub use vigil_types::{
ApprovalRequest, ApprovalResolution, ApprovalScope, ApprovalStatus, AuditEvent, DecisionKind,
DecisionRecord, EffectKind, EffectVector, ToolInvocation,
};
pub use vigil_firewall::{
EngineStatusReport, Firewall,
FirewallConfig,
FirewallError,
FirewallOutcome,
OAuthScopeContext,
PiiScanner,
};
pub use vigil_redaction::{
detect_lang_heuristic,
scan_text,
scan_text_with_engine,
scan_text_with_engine_budgeted,
scan_text_with_engine_with_hint,
BudgetedScanOutcome,
EngineStatus,
Finding,
FindingSource,
LangHintSource,
LanguageHint,
PrivacyLabel,
RedactionEngine,
RedactionResult,
RiskSignals,
ScanError,
};
pub use vigil_mcp::descriptor_hash;
pub const SDK_MODEL_ID_OPENAI_PRIVACY_FILTER_V1: &str = "openai-privacy-filter-v1";
pub const SDK_MODEL_ID_XLMR_PII_V1: &str = "xlmr-pii-v1";
pub const SDK_MODEL_ID_YONIGO_PII_V1: &str = "yonigo-pii-v1";
#[cfg(feature = "ort")]
#[derive(Debug)]
#[non_exhaustive]
pub enum EngineFactoryError {
ModelNotFound {
context: String,
},
SessionInit {
reason: String,
},
Other {
reason: String,
},
}
#[cfg(feature = "ort")]
impl std::fmt::Display for EngineFactoryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ModelNotFound { context } => write!(f, "model not found: {context}"),
Self::SessionInit { reason } => write!(f, "session init failed: {reason}"),
Self::Other { reason } => write!(f, "engine factory error: {reason}"),
}
}
}
#[cfg(feature = "ort")]
impl std::error::Error for EngineFactoryError {}
#[cfg(feature = "ort")]
impl From<vigil_redaction::engine::EngineError> for EngineFactoryError {
fn from(e: vigil_redaction::engine::EngineError) -> Self {
use vigil_redaction::engine::EngineError;
match e {
EngineError::ModelNotFound { dir } => Self::ModelNotFound { context: dir },
EngineError::SessionInit(reason) | EngineError::TokenizerLoad(reason) => {
Self::SessionInit { reason }
}
other => Self::Other {
reason: format!("{other:?}"),
},
}
}
}
#[cfg(feature = "ort")]
pub fn ort_ensemble_scanner_arc_from_env(
) -> Result<std::sync::Arc<dyn PiiScanner>, EngineFactoryError> {
vigil_firewall::ort_ensemble_scanner_arc_from_env().map_err(Into::into)
}
pub use vigil_redaction::model_descriptor::XlmrProfileMode;
#[cfg(feature = "ort")]
pub fn ort_ensemble_scanner_arc_with_xlmr_mode(
mode: XlmrProfileMode,
) -> Result<std::sync::Arc<dyn PiiScanner>, EngineFactoryError> {
vigil_firewall::ort_ensemble_scanner_arc_from_env_with_xlmr_mode(mode).map_err(Into::into)
}
pub mod prelude {
pub use crate::{
scan_text, ApprovalRequest, ApprovalResolution, ApprovalScope, ApprovalStatus, AuditEvent,
DecisionKind, DecisionRecord, EffectKind, EffectVector, Finding, FindingSource, Firewall,
FirewallConfig, FirewallError, FirewallOutcome, OAuthScopeContext, PiiScanner,
PrivacyLabel, RedactionResult, RiskSignals, ScanError, ToolInvocation,
};
}
#[doc(hidden)]
pub fn __sdk_contract_visible() {}
#[doc(hidden)]
pub fn __sdk_ensemble_v0_8_visible() {}
#[doc(hidden)]
pub fn __sdk_budgeted_visible() {}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
#[test]
fn sdk_pub_items_compile_visible() {
let _: Option<DecisionKind> = None;
let _: Option<EffectKind> = None;
let _: Option<ApprovalStatus> = None;
let _: Option<FindingSource> = Some(FindingSource::Hard);
let _: Option<PrivacyLabel> = Some(PrivacyLabel::Email);
}
#[test]
fn sdk_scan_text_default_path() {
let secret = "ghp_0123456789abcdefghijklmnopqrstuvwxyz12";
let r = scan_text(secret).expect("scan_text 成功");
assert!(
r.findings.iter().any(|f| f.kind == "github_token"),
"Hard rule 应命中 github_token,findings: {:?}",
r.findings
);
}
#[test]
fn sdk_scan_text_clean_text_no_secret_findings() {
let r = scan_text("This is a perfectly normal sentence.").expect("scan_text 成功");
assert!(
!r.findings.iter().any(|f| f.kind == "github_token"
|| f.kind == "openai_secret_key"
|| f.kind == "stripe_secret_key"),
"clean 文本不应触发任何 secret-类 hard rule"
);
}
#[test]
fn sdk_privacy_label_all_count() {
assert_eq!(
PrivacyLabel::ALL.len(),
8,
"PrivacyLabel ALL 长度变化是 SDK breaking change,需 ADR 决策"
);
}
#[test]
fn sdk_model_id_constants_match_descriptor_source() {
use vigil_redaction::model_descriptor::{
ModelDescriptor, OpenAIPrivacyFilterDescriptor, XlmrPiiDescriptor, YonigoPiiDescriptor,
};
assert_eq!(
SDK_MODEL_ID_OPENAI_PRIVACY_FILTER_V1,
OpenAIPrivacyFilterDescriptor.model_id(),
"SDK_MODEL_ID_OPENAI_PRIVACY_FILTER_V1 必须与 OpenAIPrivacyFilterDescriptor.model_id() 同源"
);
assert_eq!(
SDK_MODEL_ID_XLMR_PII_V1,
XlmrPiiDescriptor::default().model_id(),
"SDK_MODEL_ID_XLMR_PII_V1 必须与 XlmrPiiDescriptor.model_id() 同源"
);
assert_eq!(
SDK_MODEL_ID_YONIGO_PII_V1,
YonigoPiiDescriptor.model_id(),
"SDK_MODEL_ID_YONIGO_PII_V1 必须与 YonigoPiiDescriptor.model_id() 同源"
);
}
#[cfg(feature = "ort")]
#[test]
fn sdk_ort_ensemble_factory_visible_with_feature() {
let _factory: fn() -> Result<std::sync::Arc<dyn PiiScanner>, super::EngineFactoryError> =
super::ort_ensemble_scanner_arc_from_env;
}
#[test]
fn sdk_detect_lang_heuristic_advisory_only() {
let h = super::detect_lang_heuristic("田中太郎さんが昨日来ました");
assert_eq!(h.lang, "ja");
assert_eq!(h.source, super::LangHintSource::Heuristic);
assert!(h.confidence >= 0.85);
assert_eq!(
h.lang_str(),
None,
"advisory detect 必须返 None(防 production 决策 — D=C / feedback_lang_review_authoritative)"
);
let h_en = super::detect_lang_heuristic("John Smith works here.");
assert_eq!(h_en.lang, "en");
assert!(h_en.confidence < vigil_redaction::LANG_HINT_TRUSTED_CONFIDENCE);
}
#[test]
fn sdk_language_hint_typed_wrapper_visible() {
let h_caller = super::LanguageHint::caller_provided("de");
assert_eq!(h_caller.source, super::LangHintSource::CallerProvided);
assert_eq!(h_caller.lang_str(), Some("de"));
let h_fixture = super::LanguageHint::fixture("it");
assert_eq!(h_fixture.source, super::LangHintSource::FixtureExperimental);
let h_heuristic = super::LanguageHint::heuristic("de", 1.0);
assert_eq!(
h_heuristic.lang_str(),
None,
"Heuristic source 必须返 None(feedback_lang_review_authoritative 约束)"
);
let _scan: fn(
&str,
&dyn super::RedactionEngine,
Option<&super::LanguageHint>,
) -> Result<super::RedactionResult, super::ScanError> =
super::scan_text_with_engine_with_hint;
let _label = match super::LangHintSource::CallerProvided {
super::LangHintSource::CallerProvided => "caller",
super::LangHintSource::FixtureExperimental => "fixture",
super::LangHintSource::Heuristic => "heuristic",
_ => "unknown_future",
};
}
#[cfg(feature = "ort")]
#[test]
fn sdk_ort_ensemble_with_xlmr_mode_factory_visible() {
let _default = super::XlmrProfileMode::Default;
let _strict = super::XlmrProfileMode::FpStrict;
let _factory: fn(
super::XlmrProfileMode,
)
-> Result<std::sync::Arc<dyn PiiScanner>, super::EngineFactoryError> =
super::ort_ensemble_scanner_arc_with_xlmr_mode;
let _label = match super::XlmrProfileMode::Default {
super::XlmrProfileMode::Default => "default",
super::XlmrProfileMode::FpStrict => "fp_strict",
_ => "unknown_future",
};
}
#[cfg(feature = "ort")]
#[test]
fn sdk_engine_factory_error_variants_matchable() {
let e = super::EngineFactoryError::ModelNotFound {
context: "test".into(),
};
let s = format!("{e}");
assert!(s.contains("model not found"));
match e {
super::EngineFactoryError::ModelNotFound { .. } => {}
super::EngineFactoryError::SessionInit { .. } => {}
super::EngineFactoryError::Other { .. } => {}
}
}
#[test]
fn sdk_engine_status_report_pub_re_exported() {
let _ok = super::EngineStatusReport::Ok;
let _to = super::EngineStatusReport::DegradedTimeout;
let _err = super::EngineStatusReport::DegradedError;
let _un = super::EngineStatusReport::Unsupported;
}
}