chore(octavo): feature-flag octavo (atuinsh/atuin#4428)

- Ensures `atuin-client` doesn't get any `octavo` code.
- Disables `octavo` by default.
- Ensures `octavo` isn't in the next release.
This commit is contained in:
Marko Vejnovic authored and Marko Vejnovic committed 2026-10-15 22:08:02 +00:00
1 parent 9d1b5868cd
commit 756cd80455
13 files changed
+53 -37

No files matched your search

+2
View File
@@ -66,6 +66,8 @@ commit_preprocessors = [
]
# regex for parsing and grouping commits
commit_parsers = [
# Octavo is compiled out of releases until it ships.
{ message = "^\\w+\\(octavo\\)", skip = true },
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^doc", group = "Documentation" },
+1
View File
@@ -18,6 +18,7 @@ sync = ["reqwest", "dep:reqwest-middleware"]
hub = ["reqwest"]
daemon = []
check-update = []
octavo = []
# Derive the protobuf enum traits on domain enums the daemon extern-paths into its codegen.
proto = ["dep:prost"]
# Statically link (vendor) OpenSSL for native-tls instead of using the system
+10 -3
View File
@@ -538,6 +538,7 @@ pub struct PtyProxy {
pub enabled: bool,
}
#[cfg(feature = "octavo")]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Octavo {
pub enabled: bool,
@@ -1130,6 +1131,7 @@ pub struct Settings {
#[serde(default)]
pub output: OutputCapture,
#[cfg(feature = "octavo")]
#[serde(default)]
pub octavo: Octavo,
@@ -1219,6 +1221,7 @@ impl Settings {
self.data_dir.join("threads.db")
}
#[cfg(feature = "octavo")]
#[must_use]
pub fn octavo_queue_path(&self) -> PathBuf {
self.data_dir.join("octavo.db")
@@ -1527,7 +1530,7 @@ impl Settings {
let output = OutputCaptureConfig::default();
Ok(Config::builder()
let builder = Config::builder()
.set_default("history_format", "{time}\t{command}\t{duration}")?
.set_default("data_dir", data_dir.to_str())?
.set_default("db_path", db_path.to_str())?
@@ -1634,9 +1637,13 @@ impl Settings {
atuin_common::env::var_os("NO_MOTION").is_some(),
)?
.set_default("no_mouse", false)?
.set_default("pty_proxy.enabled", false)?
.set_default("pty_proxy.enabled", false)?;
#[cfg(feature = "octavo")]
let builder = builder
.set_default("octavo.enabled", false)?
.set_default("octavo.upload_output", false)?
.set_default("octavo.upload_output", false)?;
Ok(builder
.add_source(Environment::with_prefix("atuin").prefix_separator("_").separator("__")))
}
+1 -1
View File
@@ -12,7 +12,7 @@ rust-version.workspace = true
version.workspace = true
[features]
octavo = ["dep:atuin-octavo"]
octavo = ["dep:atuin-octavo", "atuin-client/octavo"]
[dependencies]
async-stream = { workspace = true }
+15 -10
View File
@@ -99,6 +99,7 @@ use tokio_stream::wrappers::BroadcastStream;
use tracing::field::Empty;
use tracing::{Instrument, Span};
#[cfg(feature = "octavo")]
use crate::octavo::Octavo;
use crate::output_capture::{CaptureError, GetOutputError, OutputCaptureEngine};
use crate::search::SearchIndex;
@@ -238,6 +239,7 @@ pub struct HistoryJournal {
/// Durable store for captured command output.
output_capture: OutputCaptureEngine,
#[cfg(feature = "octavo")]
octavo: Octavo,
/// Ids a [`Self::delete`] is currently tearing down, reference-counted across concurrent
@@ -310,7 +312,6 @@ impl HistoryJournal {
history_db: HistoryDatabase,
search_index: Arc<tokio::sync::RwLock<SearchIndex>>,
output_capture: OutputCaptureEngine,
octavo: Octavo,
) -> Self {
const DEFAULT_LIFECYCLE_SHARDS: NonZeroUsize = NonZeroUsize::new(64).unwrap();
@@ -324,12 +325,19 @@ impl HistoryJournal {
search_index,
broadcast,
output_capture,
octavo,
#[cfg(feature = "octavo")]
octavo: Octavo::nop(),
deleting: DashMap::new(),
lifecycle_mutex: AsyncShardedMutex::new(DEFAULT_LIFECYCLE_SHARDS),
}
}
/// Sends what this journal records to `octavo`, which [`Self::new`] leaves as a nop.
#[cfg(feature = "octavo")]
pub(crate) fn with_octavo(self, octavo: Octavo) -> Self {
Self { octavo, ..self }
}
/// Notify the registry that a command has been started.
///
/// Returns the [`HistoryId`] identifying the in-flight command, which is later used to
@@ -449,6 +457,7 @@ impl HistoryJournal {
// .read() and the subsequent .write() are completely discarded from the new
// index.
self.search_index.read().await.add_history(&history);
#[cfg(feature = "octavo")]
self.octavo.push_history(&history, history_record_id).await;
if self.broadcast.receiver_count() > 0 {
@@ -582,6 +591,7 @@ impl HistoryJournal {
}
// The tombstones commit the deletion, so Octavo hears of it even if a chunk below fails.
#[cfg(feature = "octavo")]
self.octavo.delete_history(&to_delete).await;
// The tombstones were just written above, so replaying them through
@@ -713,6 +723,7 @@ impl HistoryJournal {
self.output_capture.capture(id, capture).await?;
// Only once stored, since the upload reads the output back from the store.
#[cfg(feature = "octavo")]
self.octavo.push_output(id).await;
Ok(())
}
@@ -757,14 +768,8 @@ mod tests {
let history_store = HistoryStore::new(store, HostId(uuid_v7()), paseto_v4::Key::generate());
let search_index = Arc::new(RwLock::new(SearchIndex::new(OrFilter::all())));
let caps = CapClient::new("http://127.0.0.1:1".parse().unwrap(), reqwest::Client::new());
let journal = HistoryJournal::new(
caps,
history_store,
history_db,
search_index,
output_capture,
Octavo::nop(),
);
let journal =
HistoryJournal::new(caps, history_store, history_db, search_index, output_capture);
(journal, tmp)
}
+8 -4
View File
@@ -20,6 +20,7 @@ pub mod daemon;
pub mod events;
pub mod grpc;
pub(crate) mod history_journal;
#[cfg(feature = "octavo")]
mod octavo;
mod output_capture;
pub mod pidfile;
@@ -39,7 +40,6 @@ pub use history_journal::{
CmdCancelError, CmdDeleteError, CmdEvent, CmdFinishError, CmdRebuildError, FinishedCmd,
GetCmdInFlightError, HistoryJournal, RegisterOutputError,
};
pub use octavo::Octavo;
pub use output_capture::{
CaptureError, DeleteOutputError, GetOutputError, OutputCaptureEngine, OutputLine, OutputMatch,
};
@@ -352,15 +352,19 @@ pub async fn boot(
let history_store =
HistoryStore::new(handle.store().clone(), host_id, handle.encryption_key().clone());
#[cfg(feature = "octavo")]
let outputs = output_capture.store();
let journal = Arc::new(HistoryJournal::new(
let journal = HistoryJournal::new(
handle.caps().clone(),
history_store,
handle.history_db().clone(),
search_index,
output_capture,
Octavo::open(&settings, handle.clone(), outputs).await,
));
);
#[cfg(feature = "octavo")]
let journal =
journal.with_octavo(octavo::Octavo::open(&settings, handle.clone(), outputs).await);
let journal = Arc::new(journal);
let history_service = HistoryServer::new(grpc::HistoryService::new(journal, handle.clone()));
let ai_session_service =
AiSessionServer::new(grpc::AiSessionService::new(ai_session_capture.clone()));
@@ -1,8 +1,6 @@
#[cfg(feature = "octavo")]
mod active;
mod nop;
#[cfg(feature = "octavo")]
pub use active::ActiveOctavoClient;
use atuin_client::history::{History, HistoryId};
use atuin_domain::record::RecordId;
@@ -20,7 +18,6 @@ pub trait OctavoClient {
#[enum_dispatch(OctavoClient)]
#[derive(Debug)]
pub enum AnyOctavoClient {
#[cfg(feature = "octavo")]
Active(ActiveOctavoClient),
Nop(NopOctavoClient),
}
-11
View File
@@ -16,7 +16,6 @@ pub struct Octavo {
}
impl Octavo {
#[cfg(feature = "octavo")]
pub async fn open(
settings: &Settings,
handle: DaemonHandle,
@@ -39,16 +38,6 @@ impl Octavo {
}
}
#[cfg(not(feature = "octavo"))]
#[allow(clippy::unused_async)]
pub async fn open(
_settings: &Settings,
_handle: DaemonHandle,
_outputs: Arc<OutputStore>,
) -> Self {
Self::nop()
}
#[must_use]
pub fn nop() -> Self {
Self {
+1 -2
View File
@@ -30,7 +30,7 @@ use atuin_daemon::grpc::history::pb;
use atuin_daemon::grpc::history::pb::history_server::HistoryServer;
use atuin_daemon::search::{IndexFilterMode, SearchIndex};
use atuin_daemon::{
Daemon, DaemonEvent, DaemonHandle, HistoryJournal, Octavo, OutputCaptureEngine, SearchComponent,
Daemon, DaemonEvent, DaemonHandle, HistoryJournal, OutputCaptureEngine, SearchComponent,
};
use atuin_domain::record::{CmdOrigin, HostId, RecordTag};
use corpus::{HistoryGen, Seeded};
@@ -166,7 +166,6 @@ impl TestEnvBuilder {
history_db.clone(),
index.clone(),
output_capture,
Octavo::nop(),
));
let history_service =
HistoryServer::new(HistoryService::new(journal.clone(), handle.clone()));
+1
View File
@@ -41,6 +41,7 @@ xxhash-rust = { workspace = true }
[features]
# Feature-gated axum integration for the server-side capability handlers (`caps::axum`).
axum = ["dep:axum"]
octavo = []
[dev-dependencies]
pretty_assertions = { workspace = true }
+2
View File
@@ -50,11 +50,13 @@ impl Capability for PackfileCap {
}
}
#[cfg(feature = "octavo")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OctavoCap {
pub version: u32,
}
#[cfg(feature = "octavo")]
impl Capability for OctavoCap {
fn static_name() -> &'static str {
"sh.atuin.server/octavo"
+3 -1
View File
@@ -57,7 +57,9 @@ mod client;
mod middleware;
mod server;
pub use all::{CapabilitiesCap, OctavoCap, PackfileCap, PageSizeCap};
#[cfg(feature = "octavo")]
pub use all::OctavoCap;
pub use all::{CapabilitiesCap, PackfileCap, PageSizeCap};
pub use client::{AuthHeaderProvider, CapClient, ServerSupportError};
pub use middleware::{CapMiddleware, CapMismatch, CapabilitiesExt};
pub use server::{CapServer, Negotiation};
+9 -2
View File
@@ -48,14 +48,21 @@ default = [
"daemon",
"ai",
"pty-proxy",
"octavo",
]
client = ["atuin-client"]
sync = ["atuin-client/sync"]
daemon = ["dep:async-stream", "atuin-client/daemon", "atuin-daemon", "atuin-ai?/daemon"]
ai = ["atuin-ai"]
pty-proxy = ["dep:atuin-pty-proxy"]
octavo = ["sync", "daemon", "atuin-daemon/octavo", "dep:atuin-octavo"]
# Not a default feature until Octavo ships; release artifacts build without it.
octavo = [
"sync",
"daemon",
"atuin-client/octavo",
"atuin-daemon/octavo",
"atuin-domain/octavo",
"dep:atuin-octavo",
]
hex = ["pty-proxy"]
clipboard = ["dep:arboard", "atuin-ai?/clipboard"]
check-update = ["atuin-client/check-update"]