Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/tracedecay-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1371,7 +1371,7 @@ async fn dispatch_runtime_command(command: Commands) -> tracedecay_domain::error
// The MCP server is long-lived, so it may run the detached
// structured-row backfill sweep; one-shot CLI/hook processes never
// do (they would drop the sweep mid-parse on exit).
tracedecay::daemon::mark_process_long_lived_for_session_maintenance();
tracedecay_store_runtime::mark_process_long_lived_for_session_maintenance();
hotpath::future!(serve_cmd::run_serve(path, timings), label = "cli.serve.run").await?;
}
Commands::Daemon { action } => {
Expand All @@ -1392,7 +1392,7 @@ async fn dispatch_daemon_command(action: DaemonAction) -> tracedecay_domain::err
remote_tls_key,
} => {
// Long-lived host: allowed to run the structured-row sweep.
tracedecay::daemon::mark_process_long_lived_for_session_maintenance();
tracedecay_store_runtime::mark_process_long_lived_for_session_maintenance();
let socket_path = tracedecay_daemon_control::socket_path_or_default(socket)?;
let remote_tls = tracedecay_daemon_control::RemoteBrainTlsConfig::from_optional_parts(
remote_listen,
Expand Down
2 changes: 1 addition & 1 deletion crates/tracedecay-daemon-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ pub use query_mcp_admission::{
};
pub use remote_protocol::build_daemon_remote_protocol_router;
pub use request_cancellation::{Lease, RequestCancellationRegistryV1};
pub use shutdown_coordination::{ShutdownCoordinatorV1, ShutdownStatus};
pub use shutdown_coordination::ShutdownCoordinatorV1;
pub use tracedecay_daemon_protocol::{
DAEMON_INVOCATION_PROTOCOL, DAEMON_INVOCATION_REVISION, DaemonFeedbackResult,
DaemonGitEffectResult, DaemonGitPreviewResult, DaemonInvocationOperation,
Expand Down
13 changes: 1 addition & 12 deletions crates/tracedecay-daemon-service/src/shutdown_coordination.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,7 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ShutdownStatus {
Clean,
Failed(String),
TimedOut,
}

impl ShutdownStatus {
pub fn is_clean(&self) -> bool {
matches!(self, Self::Clean)
}
}
use tracedecay_store_runtime::ShutdownStatus;

pub struct ShutdownCoordinatorV1 {
state: Arc<ShutdownCoordinatorState>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};

use tracedecay_daemon_service::{ShutdownCoordinatorV1, ShutdownStatus};
use tracedecay_daemon_service::ShutdownCoordinatorV1;
use tracedecay_store_runtime::ShutdownStatus;

#[tokio::test]
async fn timed_out_waiter_does_not_cancel_the_owned_shutdown() {
Expand Down
10 changes: 10 additions & 0 deletions crates/tracedecay-maintenance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ serde_json = "1"
sha2 = "0.11"
tempfile = "3"
tokio = { version = "1", features = ["rt", "macros", "time", "sync"] }
tracing = "0.1"
tracedecay-application = { path = "../tracedecay-application", version = "0.1.0" }
tracedecay-contracts = { path = "../tracedecay-contracts", version = "0.1.0" }
tracedecay-automation = { path = "../tracedecay-automation", version = "0.1.0" }
tracedecay-code-index-runtime = { path = "../tracedecay-code-index-runtime", version = "0.1.0", default-features = false }
tracedecay-configuration = { path = "../tracedecay-configuration", version = "0.1.0" }
tracedecay-domain = { path = "../tracedecay-domain", version = "0.1.0" }
tracedecay-global-db = { path = "../tracedecay-global-db", version = "0.1.0" }
tracedecay-graph-db = { path = "../tracedecay-graph-db", version = "0.1.0" }
Expand All @@ -35,11 +39,17 @@ tracedecay-private-fs = { path = "../tracedecay-private-fs", version = "0.1.0" }
tracedecay-runtime-core = { path = "../tracedecay-runtime-core", version = "0.1.0" }
tracedecay-rusqlite-runtime = { path = "../tracedecay-rusqlite-runtime", version = "0.1.0" }
tracedecay-code-index-retention = { path = "../tracedecay-code-index-retention", version = "0.1.0" }
tracedecay-semantic-contracts.workspace = true
tracedecay-session-memory = { path = "../tracedecay-session-memory", version = "0.1.0" }
tracedecay-store = { path = "../tracedecay-store", version = "0.1.0" }
tracedecay-store-runtime = { path = "../tracedecay-store-runtime", version = "0.1.0" }
tracedecay-tool-catalog = { path = "../tracedecay-tool-catalog", version = "0.1.0" }

[target.'cfg(any(all(target_os = "linux", target_env = "gnu"), target_os = "macos"))'.dependencies]
libc = "0.2"

[dev-dependencies]
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }
filetime = "0.2"
tempfile = "3"
tokio = { version = "1", features = ["full", "test-util"] }
Expand Down
12 changes: 12 additions & 0 deletions crates/tracedecay-maintenance/src/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//! Wall-clock helpers for retention cutoffs.

use std::time::{SystemTime, UNIX_EPOCH};

/// Current Unix time in seconds, or a typed clock failure.
pub fn now_secs_i64() -> Result<i64, &'static str> {
let seconds = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| "system_clock_before_unix_epoch")?
.as_secs();
i64::try_from(seconds).map_err(|_| "system_clock_out_of_range")
}
41 changes: 41 additions & 0 deletions crates/tracedecay-maintenance/src/compaction_receipt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! Live-compaction outcome → operator receipt.

use crate::log_maintenance_event;
use crate::retention::live_compaction::LiveStoreCompactionOutcomeV1;

/// Record one live-compaction outcome and return whether the store is healthy.
#[must_use]
pub fn record_live_compaction_outcome(
store_name: &'static str,
outcome: LiveStoreCompactionOutcomeV1,
) -> bool {
match outcome {
LiveStoreCompactionOutcomeV1::NotScheduled => true,
LiveStoreCompactionOutcomeV1::Compacted {
freelist_before,
freelist_after,
} => {
log_maintenance_event(
"retention_compaction",
&[
("store", store_name.to_owned()),
(
"freed_pages",
freelist_before.saturating_sub(freelist_after).to_string(),
),
],
);
true
}
LiveStoreCompactionOutcomeV1::Failed(failure) => {
log_maintenance_event(
"retention_degraded",
&[
("pass", "compaction".to_owned()),
("failure", failure.as_str().to_owned()),
],
);
false
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
//! Ordered generation retention for one mounted project.

use super::{
MaintenanceContinuation, MaintenanceTickOutcome, StoreTelemetrySamplingRegistry,
record_live_compaction_outcome,
use crate::compaction_receipt::record_live_compaction_outcome;
use crate::lease::ProjectStoreMaintenanceLeaseV1;
use crate::retention::branch_compaction::CompactionThresholdConfig;
use crate::store_maintenance::{
CodeGenerationRetentionOutcomeV1, run_branch_compaction, run_code_generation_retention,
run_code_index_scope_reconciliation, run_semantic_vector_generation_retention,
};
use crate::daemon::store_maintenance::CodeGenerationRetentionOutcomeV1;
use crate::telemetry::StoreTelemetrySamplingRegistry;
use crate::tick::{MaintenanceContinuation, MaintenanceTickOutcome};

/// Run the production generation-maintenance journey for one mounted project.
/// Run the production generation-maintenance journey for one admitted store lease.
///
/// Vector generations converge before their source code generations can be
/// collected. Scope deletion is admitted only from a complete
Expand All @@ -24,21 +28,18 @@ use crate::daemon::store_maintenance::CodeGenerationRetentionOutcomeV1;
/// draining a superseded backlog on the short cadence without re-running
/// scope reconciliation or compaction.
#[hotpath::measure(label = "daemon.maintenance.generation", future = true)]
pub(in crate::daemon) async fn run_project_generation_maintenance(
graph: &crate::tracedecay::TraceDecay,
pub async fn run_project_generation_maintenance(
lease: &ProjectStoreMaintenanceLeaseV1,
code_index_schedulers: &tracedecay_code_index_runtime::code_index_scheduler::CodeIndexSchedulerRegistryV1,
maintenance_observations: &StoreTelemetrySamplingRegistry,
cancellation: &tracedecay_session_memory::context::CancellationToken,
retention: &crate::config::RetentionConfig,
compaction: Option<&CompactionThresholdConfig>,
continuation: Option<MaintenanceContinuation>,
) -> MaintenanceTickOutcome {
// Each ordered phase gets its own wall span: the outer generation span is
// inclusive, so a slow tick is attributed to vector retention, code
// generation retention, scope reconciliation, or compaction — not guessed.
let mut outcome = hotpath::measure_block!(
"daemon.maintenance.vector_retention",
crate::daemon::store_maintenance::run_semantic_vector_generation_retention(
graph,
run_semantic_vector_generation_retention(
lease,
code_index_schedulers,
maintenance_observations,
cancellation,
Expand All @@ -54,8 +55,8 @@ pub(in crate::daemon) async fn run_project_generation_maintenance(
} else {
hotpath::measure_block!(
"daemon.maintenance.code_generation_retention",
crate::daemon::store_maintenance::run_code_generation_retention(
graph,
run_code_generation_retention(
lease,
code_index_schedulers,
maintenance_observations,
cancellation,
Expand All @@ -82,12 +83,12 @@ pub(in crate::daemon) async fn run_project_generation_maintenance(
if semantic_collection_complete
&& code_generation == CodeGenerationRetentionOutcomeV1::Complete
&& !cancellation.is_cancelled()
&& maintenance_observations.semantic_vector_scope_collection_ready(graph.project_root())
&& maintenance_observations.semantic_vector_scope_collection_ready(lease.project_root())
{
let scope_reconciled = hotpath::measure_block!(
"daemon.maintenance.scope_reconciliation",
crate::daemon::store_maintenance::run_code_index_scope_reconciliation(
graph,
run_code_index_scope_reconciliation(
lease,
code_index_schedulers,
maintenance_observations,
)
Expand All @@ -98,13 +99,13 @@ pub(in crate::daemon) async fn run_project_generation_maintenance(
}
}
if !cancellation.is_cancelled()
&& let Some(compaction) = &retention.compaction
&& let Some(compaction) = compaction
{
hotpath::measure_block!("daemon.maintenance.compaction", {
let project_compacted = record_live_compaction_outcome(
crate::config::DB_FILENAME,
tracedecay_maintenance::retention::live_compaction::compact_project_store(
graph.db(),
tracedecay_runtime_core::config::DB_FILENAME,
crate::retention::live_compaction::compact_project_store(
lease.graph_db(),
compaction,
)
.await,
Expand All @@ -113,9 +114,7 @@ pub(in crate::daemon) async fn run_project_generation_maintenance(
outcome = MaintenanceTickOutcome::Retry;
}
if !cancellation.is_cancelled() {
let branch_compacted =
crate::daemon::store_maintenance::run_branch_compaction(graph, compaction)
.await;
let branch_compacted = run_branch_compaction(lease, compaction).await;
if !branch_compacted {
outcome = MaintenanceTickOutcome::Retry;
}
Expand All @@ -125,8 +124,6 @@ pub(in crate::daemon) async fn run_project_generation_maintenance(
finalize_generation_outcome(outcome, cancellation)
}

/// Cancelled and degraded ticks are recorded too: a maintenance lane that
/// silently retries forever is exactly the waste being diagnosed.
fn finalize_generation_outcome(
outcome: MaintenanceTickOutcome,
cancellation: &tracedecay_session_memory::context::CancellationToken,
Expand Down
75 changes: 75 additions & 0 deletions crates/tracedecay-maintenance/src/lease.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Admitted project-store lease for maintenance kernels.
//!
//! Callers extract these fields from a mounted project store. Kernels never
//! name the composition-root aggregate.

use std::path::{Path, PathBuf};
use std::sync::Arc;

use tracedecay_configuration::ProjectConfigurationRuntime;
use tracedecay_global_db::RegisteredGlobalDbLeaseV1;
use tracedecay_runtime_core::db::Database;
use tracedecay_runtime_core::storage::StoreLayout;
use tracedecay_store_runtime::DaemonSessionRuntimeRegistryV1;

/// Registered store lease for one mounted project's maintenance journey.
#[derive(Clone)]
pub struct ProjectStoreMaintenanceLeaseV1 {
project_root: PathBuf,
store_layout: StoreLayout,
graph_db: Database,
store_runtime: Arc<DaemonSessionRuntimeRegistryV1>,
configuration_runtime: Arc<ProjectConfigurationRuntime>,
profile_database: RegisteredGlobalDbLeaseV1,
}

impl ProjectStoreMaintenanceLeaseV1 {
#[must_use]
pub fn new(
project_root: PathBuf,
store_layout: StoreLayout,
graph_db: Database,
store_runtime: Arc<DaemonSessionRuntimeRegistryV1>,
configuration_runtime: Arc<ProjectConfigurationRuntime>,
profile_database: RegisteredGlobalDbLeaseV1,
) -> Self {
Self {
project_root,
store_layout,
graph_db,
store_runtime,
configuration_runtime,
profile_database,
}
}

#[must_use]
pub fn project_root(&self) -> &Path {
&self.project_root
}

#[must_use]
pub fn store_layout(&self) -> &StoreLayout {
&self.store_layout
}

#[must_use]
pub fn graph_db(&self) -> &Database {
&self.graph_db
}

#[must_use]
pub fn store_runtime(&self) -> &Arc<DaemonSessionRuntimeRegistryV1> {
&self.store_runtime
}

#[must_use]
pub fn configuration_runtime(&self) -> &Arc<ProjectConfigurationRuntime> {
&self.configuration_runtime
}

#[must_use]
pub fn profile_database(&self) -> &RegisteredGlobalDbLeaseV1 {
&self.profile_database
}
}
Loading
Loading