#![recursion_limit = "256"]
pub mod analysis_pipeline;
pub mod analysis_publication;
pub mod analytics;
mod background_refresh;
mod cli;
pub mod config;
pub(crate) mod corrections;
pub mod dates;
pub mod db;
pub mod diagnostics;
mod durable_fs;
pub(crate) mod durable_path;
mod executable_alias;
pub mod export;
pub mod files;
mod fts;
pub mod hashing;
pub mod indexer;
pub mod inspect;
pub(crate) mod integrations;
pub mod keymap;
pub mod mcp_schema_budget;
pub mod mcp_server;
pub(crate) mod message_classification;
pub mod message_search;
mod message_search_batches;
pub mod messages;
pub mod migration;
pub mod models;
pub mod terminal_style;
mod text_file_transaction;
#[cfg_attr(not(test), warn(clippy::unwrap_used))]
pub mod providers;
pub mod refs;
pub mod render;
pub mod runtime;
pub mod search_scope;
pub mod service;
pub mod skill_catalog;
pub(crate) mod skill_manifest;
pub mod skill_run;
pub mod skills;
pub mod source;
mod sql_functions;
pub mod sql_query;
pub mod tail;
pub mod trigram;
pub mod trigram_index;
mod tui;
pub(crate) mod update;
pub mod util;
pub use analysis_pipeline::{
AnalysisPolicySpec, AnalysisResult, ClassificationRuleSpec, ClassificationTarget,
PhraseTextMode, PhraseVocabularyPolicySpec,
};
pub use analysis_publication::{
AnalysisPublicationFormat, AnalysisPublicationPlan, AnalysisPublicationReceipt,
};
pub use cli::run_from as run_cli_from;
pub use corrections::{CapabilityReceipt, MessageClassificationReport};
pub use export::{ExportFormat, ExportPublicationPlan};
pub use inspect::{EvidenceWindow, InspectionOptions};
pub use message_search::{
AdditionalFieldText, ContextWindow, CoordinateUnit, DetailLevel, FieldViewBudget,
FieldViewExtent, FuzzyQuery, JsonPointer, LineWindow, LiteralQuery, MatchViewBudget,
MatchWindow, MessageFieldView, MessageMatchEvidence, MessageMatchViewMarkers,
MessagePredicates, MessageQuery, MessageResultRef, MessageSearchError, MessageSearchHit,
MessageSearchInclude, MessageSearchIncludedData, MessageSearchOmission, MessageSearchParameter,
MessageSearchParameterDomain, MessageSearchParameterRegistry, MessageSearchParameterSpec,
MessageSearchRequest, MessageSearchRequestBuilder, MessageSearchResponse, MessageSearchRule,
MessageSearchRuntimeDiagnostics, MessageSearchSpecification, MessageTarget, PageSide,
ProviderScope, PurposeSelection, ReceiptLevel, RequestedExtent, RequestedTimeRange,
ResolvedMessageSearchRequest, ResolvedQueryMode, ResolvedRequestExtent,
ResolvedRequestPresentation, ResultSetExtent, SequenceRange, ValidatedRegex, ValueOriginKind,
ViewCharRange,
};
pub use message_search_batches::{
MessageSearchBatch, MessageSearchBatches, MessageSearchCompletion,
};
pub use models::{AnalysisRequest, AnalysisSessionSelection};
pub use models::{
FileQuery, MessageClassificationMatch, MessageFilters, MessageSearchMode, SearchFilters,
SessionKind,
};
pub use search_scope::{
AccessRoot, AccessRootOrigin, AccessRootSource, EffectiveAccessScope, TrustedAccessInputs,
};
pub use service::SessionSearch;
pub use service::{AnalysisReceipt, ReceiptedAnalysis};
pub use skill_catalog::{SkillName, SkillNameSelector, SkillPathSelector, SkillSelector};
pub use skill_run::{
CapabilityExecutionSource, MessageClassificationDefinition, MessageClassificationQuery,
MessageClassificationResult, ResolvedSkillReceipt, SelectedSkillLocation, SkillCapabilityInput,
SkillCapabilityOutput, SkillRunQuery, SkillRunReport,
};
#[doc(hidden)]
pub fn run_cli_from_python<I, T>(args: I) -> anyhow::Result<i32>
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
{
update::mark_python_binding_process();
cli::run_from(args)
}
#[doc(hidden)]
pub fn is_broken_pipe_error(error: &anyhow::Error) -> bool {
error.chain().any(|cause| {
cause
.downcast_ref::<std::io::Error>()
.is_some_and(|error| error.kind() == std::io::ErrorKind::BrokenPipe)
|| cause
.downcast_ref::<serde_json::Error>()
.is_some_and(|error| error.io_error_kind() == Some(std::io::ErrorKind::BrokenPipe))
})
}
#[doc(hidden)]
pub fn is_storage_full_error(error: &anyhow::Error) -> bool {
error.chain().any(|cause| {
cause
.downcast_ref::<std::io::Error>()
.is_some_and(|error| error.kind() == std::io::ErrorKind::StorageFull)
|| cause
.downcast_ref::<serde_json::Error>()
.is_some_and(|error| error.io_error_kind() == Some(std::io::ErrorKind::StorageFull))
|| cause
.downcast_ref::<rusqlite::Error>()
.and_then(rusqlite::Error::sqlite_error_code)
.is_some_and(|code| code == rusqlite::ErrorCode::DiskFull)
})
}
const STORAGE_FULL_GUIDANCE: &str =
"There is no free space left for this write. No partial file replaced a complete one: file \
writes publish by atomic rename and database writes roll back, so the history already indexed \
is intact. Free space, then rerun; `aise doctor` reports index readiness.";
#[doc(hidden)]
pub fn error_message_with_recovery(error: &anyhow::Error) -> String {
let rendered = format!("{error:#}");
if is_storage_full_error(error) {
return format!("{rendered}\n{STORAGE_FULL_GUIDANCE}");
}
rendered
}
#[cfg(test)]
mod storage_pressure_tests {
#[test]
fn storage_exhaustion_is_recognized_from_the_filesystem_and_from_sqlite() {
let staged = anyhow::Error::new(std::io::Error::from(std::io::ErrorKind::StorageFull))
.context("failed to write staging file /tmp/index.db.stage");
assert!(super::is_storage_full_error(&staged), "{staged:#}");
let database = anyhow::Error::new(rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_FULL),
Some("database or disk is full".to_string()),
))
.context("could not commit the index transaction");
assert!(super::is_storage_full_error(&database), "{database:#}");
}
#[test]
fn a_storage_failure_is_rendered_with_what_survived_and_what_to_do() {
let rendered = super::error_message_with_recovery(
&anyhow::Error::new(std::io::Error::from(std::io::ErrorKind::StorageFull))
.context("failed to write staging file"),
);
assert!(
rendered.contains("failed to write staging file"),
"{rendered}"
);
assert!(rendered.contains("no free space"), "{rendered}");
assert!(rendered.contains("is intact"), "{rendered}");
assert!(rendered.contains("aise doctor"), "{rendered}");
assert!(!rendered.contains("searchable"), "{rendered}");
}
#[test]
fn an_unrelated_failure_gains_no_storage_guidance() {
let error = anyhow::Error::new(std::io::Error::from(std::io::ErrorKind::PermissionDenied))
.context("failed to write staging file");
assert_eq!(
super::error_message_with_recovery(&error),
format!("{error:#}")
);
}
#[test]
fn other_failures_are_not_reported_as_storage_exhaustion() {
for error in [
anyhow::Error::new(std::io::Error::from(std::io::ErrorKind::NotFound)),
anyhow::Error::new(std::io::Error::from(std::io::ErrorKind::PermissionDenied)),
anyhow::Error::new(rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_BUSY),
Some("database is locked".to_string()),
)),
] {
assert!(!super::is_storage_full_error(&error), "{error:#}");
}
}
}