Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
740052a
fix(sessions): refuse an authoritative zero from a partial generation
ScriptedAlchemy Sep 19, 2026
5e03dfa
fix(daemon): compare version identity per semver build rules
ScriptedAlchemy Sep 19, 2026
cc10394
fix(sessions): retire refused refresh progress instead of retrying
ScriptedAlchemy Sep 19, 2026
8b75523
perf(code-index): scan clone postings once per resumed page
ScriptedAlchemy Sep 19, 2026
e2384db
fix(sessions): retire only refused progress, not a cancelled pass
ScriptedAlchemy Sep 19, 2026
f2ab808
perf(code-index): index clone postings by occurrence for resume replay
ScriptedAlchemy Sep 19, 2026
f77e763
test(global-db): gate analytics append on abandonment, not poll
ScriptedAlchemy Sep 19, 2026
53d7f9d
fix(retention): skip an artifact reclaimed during the scan
ScriptedAlchemy Sep 19, 2026
b130bce
test(daemon): defer the retention plan while the store is busy
ScriptedAlchemy Sep 19, 2026
63c0784
fix(mcp): attribute risky sites by byte span, not line
ScriptedAlchemy Sep 19, 2026
6d8ef97
test(application): keep git auto-maintenance out of the fixture
ScriptedAlchemy Sep 19, 2026
b05cab2
fix(daemon): mount the published branch worktree's query authority
ScriptedAlchemy Sep 19, 2026
7c22827
fix(retention): bound store locks by cancel and deadlines
cursoragent Sep 19, 2026
de5f920
fix(mcp): attribute field sites by byte span, not line
ScriptedAlchemy Sep 19, 2026
aab865a
test(runtime): settle the worker before sampling elapsed freshness
ScriptedAlchemy Sep 19, 2026
15f25b3
test(cli): keep the hotpath metrics port out of the quiet-pipeline test
ScriptedAlchemy Sep 19, 2026
6eecf5f
test(retention): assert a vanished census open is absent
cursoragent Sep 19, 2026
dfbcb15
Merge remote-tracking branch 'origin/master' into fix/master-ci-green-3
ScriptedAlchemy Sep 19, 2026
fc4b848
style(global-db): format the observation collision tests
ScriptedAlchemy Sep 19, 2026
f550e77
fix(hermes): converge past refused rows instead of eternal skip
ScriptedAlchemy Sep 19, 2026
278d18f
fix(ingest): treat a peer-covered cursor CAS loss as a no-op
ScriptedAlchemy Sep 19, 2026
19ba2b6
test(codex): count replay index visits per thread
ScriptedAlchemy Sep 19, 2026
9f8092d
test(ingest): keep the shared meta cache off its degraded mode
ScriptedAlchemy Sep 19, 2026
822b181
fix(session-temporal): drop the no-op drop in the doctor test
ScriptedAlchemy Sep 19, 2026
4e1a203
Merge remote-tracking branch 'origin/master' into fix/master-ci-green-3
ScriptedAlchemy Sep 19, 2026
8f33ad6
test(sessions): re-reasoned cursor replay is a duplicate
ScriptedAlchemy Sep 19, 2026
344d15e
Merge remote-tracking branch 'origin/fix/master-ci-green-3' into curs…
ScriptedAlchemy Sep 19, 2026
014ba1d
fix(graph-publication): bound begin by wall clock, not one attempt
ScriptedAlchemy Sep 19, 2026
1dca5ba
test(retention): observe pool acquires per thread, not per process
ScriptedAlchemy Sep 19, 2026
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
8 changes: 8 additions & 0 deletions crates/tracedecay-application/src/git_intelligence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1762,6 +1762,14 @@ mod tests {
"user.email=fixture@example.com",
"-c",
"commit.gpgsign=false",
// `git commit` spawns a detached `git maintenance run --auto`
// that holds `.git/objects/maintenance.lock` after the commit
// returns; the byte-identical snapshot must not see it appear
// or vanish between its two walks.
"-c",
"maintenance.auto=false",
"-c",
"gc.auto=0",
])
.args(args)
.current_dir(self.path())
Expand Down
4 changes: 4 additions & 0 deletions crates/tracedecay-cli/tests/core_cli_suite/cli_boundary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ fn shipped_binary_stops_quietly_when_a_pipeline_reader_exits() {
let output = Command::new("sh")
.args(["-c", r#""$TRACEDECAY_BIN" tool | head -n 4"#])
.env("TRACEDECAY_BIN", env!("CARGO_BIN_EXE_tracedecay"))
// A hotpath-enabled binary binds its metrics port on start; when a
// sibling test's daemon already holds it, the bind failure lands on
// stderr and breaks the quiet-pipeline assertion below.
.env("HOTPATH_METRICS_SERVER_OFF", "true")
.output()
.expect("tracedecay tool pipeline should run");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -899,8 +899,11 @@ fn plan_code_generation_retention_with_verification_cancellable(
let Some(file_name) = generation_file_name(&path) else {
continue;
};
let (format_revision, manifest, raw_state_digest, size_bytes) =
read_generation_metadata(&path, verification, is_cancelled)?;
let Some((format_revision, manifest, raw_state_digest, size_bytes)) =
read_generation_metadata(&path, verification, is_cancelled)?
else {
continue;
};
let expected_file = format!(
"generation-{}.json",
sha256_hex_suffix(&raw_state_digest).unwrap_or(&raw_state_digest)
Expand Down Expand Up @@ -1212,13 +1215,18 @@ fn sweep_unreferenced_generation_segments(
})?
.to_owned()
};
if read_generation_format_revision(&path, is_cancelled)?
!= SEALED_GENERATION_FORMAT_REVISION_V1
{
let Some(revision) = read_generation_format_revision(&path, is_cancelled)? else {
continue;
};
if revision != SEALED_GENERATION_FORMAT_REVISION_V1 {
continue;
}
let mut reader = CancellableGenerationManifestReaderV1 {
file: File::open(&path).map_err(storage)?,
file: match File::open(&path) {
Ok(file) => file,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
Err(error) => return Err(storage(error)),
},
hasher: Sha256::new(),
is_cancelled,
cancelled: false,
Expand Down Expand Up @@ -1281,7 +1289,11 @@ fn sweep_unreferenced_generation_segments(
if live_segments.contains(&format!("sha256:{digest}")) {
continue;
}
let metadata = path.symlink_metadata().map_err(storage)?;
let metadata = match path.symlink_metadata() {
Ok(metadata) => metadata,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => continue,
Err(error) => return Err(storage(error)),
};
if !metadata.file_type().is_file() {
return Err(CodeGenerationRetentionErrorV1::UnsafeState(format!(
"generation segment '{}' is not a regular file",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ const MAX_FORMAT_REVISION_PREFIX_BYTES: usize = 4 * 1024;
pub(super) fn read_generation_format_revision(
path: &Path,
is_cancelled: &dyn Fn() -> bool,
) -> Result<u32, CodeGenerationRetentionErrorV1> {
let mut file = File::open(path).map_err(storage)?;
) -> Result<Option<u32>, CodeGenerationRetentionErrorV1> {
let mut file = match File::open(path) {
Ok(file) => file,
// The directory entry was removed between listing and open. That is
// concurrent publication, not a broken store.
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(error) => return Err(storage(error)),
};
let mut prefix = vec![0_u8; MAX_FORMAT_REVISION_PREFIX_BYTES];
let bytes_read = file.read(&mut prefix).map_err(storage)?;
crate::hotpath_observe::retention_inspected(bytes_read as u64);
Expand All @@ -25,22 +31,30 @@ pub(super) fn read_generation_format_revision(
return Err(CodeGenerationRetentionErrorV1::Cancelled);
}
prefix.truncate(bytes_read);
parse_json_u32_field(&prefix, b"format_revision").ok_or_else(|| {
CodeGenerationRetentionErrorV1::UnsafeState(format!(
"generation file '{}' has no readable format revision in its bounded prefix",
path.display()
))
})
parse_json_u32_field(&prefix, b"format_revision")
.ok_or_else(|| {
CodeGenerationRetentionErrorV1::UnsafeState(format!(
"generation file '{}' has no readable format revision in its bounded prefix",
path.display()
))
})
.map(Some)
}

#[hotpath::measure(label = "usecases.retention.read_metadata")]
pub(super) fn read_generation_metadata(
path: &Path,
verification: GenerationDigestVerificationV1,
is_cancelled: &dyn Fn() -> bool,
) -> Result<(u32, SealedGenerationManifestMetadataV1, String, u64), CodeGenerationRetentionErrorV1>
{
let mut file = File::open(path).map_err(storage)?;
) -> Result<
Option<(u32, SealedGenerationManifestMetadataV1, String, u64)>,
CodeGenerationRetentionErrorV1,
> {
let mut file = match File::open(path) {
Ok(file) => file,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(error) => return Err(storage(error)),
};
let size_bytes = file.metadata().map_err(storage)?.len();
let mut hasher = Sha256::new();
let mut prefix = Vec::with_capacity(MAX_GENERATION_METADATA_PREFIX_BYTES);
Expand Down Expand Up @@ -91,7 +105,7 @@ pub(super) fn read_generation_metadata(
}
GenerationDigestVerificationV1::MetadataOnly => named_state_digest(path)?,
};
Ok((format_revision, manifest, state_digest, size_bytes))
Ok(Some((format_revision, manifest, state_digest, size_bytes)))
}

fn named_state_digest(path: &Path) -> Result<String, CodeGenerationRetentionErrorV1> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
//!
//! Quarantined generations are journaled, then hard-linked into the replay pool before the receipt is durable.

#[cfg(test)]
use std::cell::Cell;
use std::collections::BTreeSet;
use std::fs::File;
use std::io::Read;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
#[cfg(test)]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Instant;

use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -264,23 +264,30 @@ pub(super) fn acquire_graph_replay_pool_lock_checked(
GraphReplayPoolLockV1::acquire_exclusive(pool_root, deadline, is_cancelled)
}

// Per-thread, not process-wide: an acquire runs on its caller's thread, and
// the test harness runs the other acquire tests in parallel on their own
// threads. Shared statics let any concurrent acquire land between a test's
// reset and its read, which is what turned the exact `(1, 0)` proof into an
// occasional `(5, 3)`.
#[cfg(test)]
static GRAPH_REPLAY_POOL_ACQUIRE_TRIES: AtomicUsize = AtomicUsize::new(0);
#[cfg(test)]
static GRAPH_REPLAY_POOL_ACQUIRE_WAITS: AtomicUsize = AtomicUsize::new(0);
thread_local! {
static GRAPH_REPLAY_POOL_ACQUIRE_TRIES: Cell<usize> = const { Cell::new(0) };
static GRAPH_REPLAY_POOL_ACQUIRE_WAITS: Cell<usize> = const { Cell::new(0) };
}

#[cfg(test)]
pub(super) fn reset_graph_replay_pool_acquire_observation() {
GRAPH_REPLAY_POOL_ACQUIRE_TRIES.store(0, Ordering::SeqCst);
GRAPH_REPLAY_POOL_ACQUIRE_WAITS.store(0, Ordering::SeqCst);
GRAPH_REPLAY_POOL_ACQUIRE_TRIES.with(|tries| tries.set(0));
GRAPH_REPLAY_POOL_ACQUIRE_WAITS.with(|waits| waits.set(0));
}

/// `(non_blocking_tries, wait_for_exclusive_calls)` since the last reset.
/// `(non_blocking_tries, wait_for_exclusive_calls)` on this thread since the
/// last reset.
#[cfg(test)]
pub(super) fn graph_replay_pool_acquire_observation() -> (usize, usize) {
(
GRAPH_REPLAY_POOL_ACQUIRE_TRIES.load(Ordering::SeqCst),
GRAPH_REPLAY_POOL_ACQUIRE_WAITS.load(Ordering::SeqCst),
GRAPH_REPLAY_POOL_ACQUIRE_TRIES.with(Cell::get),
GRAPH_REPLAY_POOL_ACQUIRE_WAITS.with(Cell::get),
)
}

Expand All @@ -307,7 +314,7 @@ impl GraphReplayPoolLockV1 {
// the budget is gone. Windows lock-conflict is `Ok(None)` via
// `is_lock_contended`, not Storage.
#[cfg(test)]
GRAPH_REPLAY_POOL_ACQUIRE_TRIES.fetch_add(1, Ordering::SeqCst);
GRAPH_REPLAY_POOL_ACQUIRE_TRIES.with(|tries| tries.set(tries.get() + 1));
match try_acquire_code_generation_store_lock(pool_root)? {
Some(guard) => {
crate::hotpath_observe::retention_replay_pool_acquired();
Expand All @@ -327,7 +334,7 @@ impl GraphReplayPoolLockV1 {

fn wait_for_exclusive(deadline: Instant) {
#[cfg(test)]
GRAPH_REPLAY_POOL_ACQUIRE_WAITS.fetch_add(1, Ordering::SeqCst);
GRAPH_REPLAY_POOL_ACQUIRE_WAITS.with(|waits| waits.set(waits.get() + 1));
crate::hotpath_observe::retention_replay_pool_acquire_wait();
let remaining = deadline.saturating_duration_since(Instant::now());
if remaining.is_zero() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use std::fs::{File, OpenOptions};
use std::path::{Path, PathBuf};
use std::time::Instant;

use super::{CodeGenerationRetentionErrorV1, SCOPE_RETENTION_LOCK_FILE, STORE_LOCK_FILE, storage};
use super::{
CodeGenerationRetentionErrorV1, GRAPH_REPLAY_POOL_ACQUIRE_BUDGET,
GRAPH_REPLAY_POOL_ACQUIRE_POLL, SCOPE_RETENTION_LOCK_FILE, STORE_LOCK_FILE, storage,
};

pub struct CodeGenerationStoreLockV1 {
file: File,
Expand Down Expand Up @@ -36,7 +40,33 @@ impl Drop for CodeGenerationStoreLockV1 {
pub fn acquire_code_generation_store_lock(
store_root: &Path,
) -> Result<CodeGenerationStoreLockV1, CodeGenerationRetentionErrorV1> {
lock_file(store_root, STORE_LOCK_FILE, true)
acquire_code_generation_store_lock_checked(
store_root,
Instant::now() + GRAPH_REPLAY_POOL_ACQUIRE_BUDGET,
&|| false,
)
}

/// Exclusive generation-store lock that stops at `deadline` or cancellation.
///
/// A free lock is taken even when the deadline has already elapsed, so a
/// caller that only needs one uncontended critical section is not refused.
/// A held lock returns [`CodeGenerationRetentionErrorV1::GenerationStoreBusy`]
/// or [`CodeGenerationRetentionErrorV1::Cancelled`] instead of blocking in
/// `File::lock`, which cannot observe either signal.
pub fn acquire_code_generation_store_lock_checked(
store_root: &Path,
deadline: Instant,
is_cancelled: &dyn Fn() -> bool,
) -> Result<CodeGenerationStoreLockV1, CodeGenerationRetentionErrorV1> {
lock_file_checked(
store_root,
STORE_LOCK_FILE,
true,
deadline,
is_cancelled,
CodeGenerationRetentionErrorV1::GenerationStoreBusy,
)
}

/// Try to hold the generation store as a reader for one bounded read of
Expand Down Expand Up @@ -81,24 +111,63 @@ pub fn try_acquire_code_generation_store_lock(
pub(super) fn acquire_scope_retention_lock(
store_root: &Path,
) -> Result<CodeGenerationStoreLockV1, CodeGenerationRetentionErrorV1> {
lock_file(store_root, SCOPE_RETENTION_LOCK_FILE, false)
acquire_scope_retention_lock_checked(
store_root,
Instant::now() + GRAPH_REPLAY_POOL_ACQUIRE_BUDGET,
&|| false,
)
}

pub(super) fn acquire_scope_retention_lock_checked(
store_root: &Path,
deadline: Instant,
is_cancelled: &dyn Fn() -> bool,
) -> Result<CodeGenerationStoreLockV1, CodeGenerationRetentionErrorV1> {
lock_file_checked(
store_root,
SCOPE_RETENTION_LOCK_FILE,
false,
deadline,
is_cancelled,
CodeGenerationRetentionErrorV1::GenerationStoreBusy,
)
}

#[hotpath::measure(label = "code_index_retention.lock")]
fn lock_file(
fn lock_file_checked(
store_root: &Path,
lock_file: &str,
generation_store: bool,
deadline: Instant,
is_cancelled: &dyn Fn() -> bool,
busy: CodeGenerationRetentionErrorV1,
) -> Result<CodeGenerationStoreLockV1, CodeGenerationRetentionErrorV1> {
let store_root = canonical_store_root(store_root)?;
let lock = open_lock_file(&store_root.join(lock_file))?;
lock.lock().map_err(storage)?;
Ok(CodeGenerationStoreLockV1 {
file: lock,
store_root,
generation_store,
shared: false,
})
let deadline = deadline.min(Instant::now() + GRAPH_REPLAY_POOL_ACQUIRE_BUDGET);
loop {
if is_cancelled() {
return Err(CodeGenerationRetentionErrorV1::Cancelled);
}
let lock = open_lock_file(&store_root.join(lock_file))?;
match lock.try_lock().map_err(std::io::Error::from) {
Ok(()) => {
return Ok(CodeGenerationStoreLockV1 {
file: lock,
store_root,
generation_store,
shared: false,
});
}
Err(error) if tracedecay_private_fs::is_lock_contended(&error) => {
if Instant::now() >= deadline {
return Err(busy);
}
let remaining = deadline.saturating_duration_since(Instant::now());
std::thread::park_timeout(remaining.min(GRAPH_REPLAY_POOL_ACQUIRE_POLL));
}
Err(error) => return Err(storage(error)),
}
}
}

fn canonical_store_root(store_root: &Path) -> Result<PathBuf, CodeGenerationRetentionErrorV1> {
Expand Down
Loading