Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5fb49c3
fix(global-db): skip retention-released Codex alias owners
ScriptedAlchemy Oct 20, 2026
22ec39b
Merge master and preserve terminal refusal preflight
ScriptedAlchemy Oct 20, 2026
07be013
Merge branch 'master' into devin/1792524585-codex-sibling-released-owner
ScriptedAlchemy Oct 20, 2026
f12f43b
Merge branch 'master' into devin/1792524585-codex-sibling-released-owner
ScriptedAlchemy Oct 20, 2026
6a48c2f
Merge branch 'master' into devin/1792524585-codex-sibling-released-owner
ScriptedAlchemy Oct 21, 2026
37fa9cd
Merge branch 'master' into devin/1792524585-codex-sibling-released-owner
ScriptedAlchemy Oct 21, 2026
5498869
test(sessions): verify durable completion after rekey
ScriptedAlchemy Oct 21, 2026
9ec4a6c
Merge branch 'master' into devin/1792524585-codex-sibling-released-owner
ScriptedAlchemy Oct 21, 2026
6ec6276
Merge branch 'master' into devin/1792524585-codex-sibling-released-owner
ScriptedAlchemy Oct 21, 2026
15e808c
test(hooks): verify capture outcomes for every callback
ScriptedAlchemy Oct 21, 2026
85bd4c7
fix(memory): reserve text builds below the admission ceiling
ScriptedAlchemy Oct 21, 2026
4eca194
test(cli): retain daemon evidence for parked index failures
ScriptedAlchemy Oct 21, 2026
25b8aed
test(dashboard): expose explorer timeout state in CI failures
ScriptedAlchemy Oct 21, 2026
1348d65
fix(memory): size text reservations inside ledger admission
ScriptedAlchemy Oct 21, 2026
d240775
fix(memory): isolate fixtures and preserve admission progress
ScriptedAlchemy Oct 21, 2026
90776a5
Merge remote-tracking branch 'origin/master' into devin/1792524585-co…
ScriptedAlchemy Oct 21, 2026
d93ac28
Merge remote-tracking branch 'origin/master' into devin/1792524585-co…
ScriptedAlchemy Oct 21, 2026
3a570a0
test(sessions): retain rekey completion checks after merge
ScriptedAlchemy Oct 22, 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
65 changes: 34 additions & 31 deletions crates/tracedecay-agent-hosts/src/agents/claude/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::super::{load_json_file_strict, safe_write_json_file, safe_write_text_file};
use super::super::{
load_json_file_strict, safe_write_json_file, safe_write_text_file,
unexpected_bundle_entrypoints,
};
use super::*;
use serde_json::json;
#[cfg(unix)]
Expand Down Expand Up @@ -148,46 +151,46 @@ fn native_cache_content_drift_and_binary_relocation_require_refresh() {
.unwrap()
);
copy_rendered_bundle_to_native_cache(home.path(), new_bin);
let source = plugin_deploy_dir(home.path());
let cache = claude_current_cached_plugin_root(home.path());
let rendered = rendered_plugin_files(new_bin, &ServeArgs::default()).unwrap();
let relatives = rendered
.iter()
.map(|(relative, _)| (*relative).to_string())
.map(|(relative, _)| (*relative).to_owned())
.collect::<Vec<_>>();
for root in [
plugin_deploy_dir(home.path()),
claude_current_cached_plugin_root(home.path()),
] {
for (relative, expected) in &rendered {
let path = root.join(relative);
let actual = std::fs::read(&path).unwrap();
assert!(
actual == expected.as_bytes(),
"relocated bundle differs at {}: actual bytes={}, expected bytes={}, first differing byte={:?}",
path.display(),
actual.len(),
expected.len(),
actual
.iter()
.zip(expected.as_bytes())
.position(|(actual, expected)| actual != expected)
);
}
for (relative, expected) in rendered {
assert_eq!(
super::super::unexpected_bundle_entrypoints(
&root,
&relatives,
&[".claude-plugin", "agents", "commands", "hooks", "skills"],
)
.unwrap(),
Vec::<String>::new(),
"relocation left auto-discovered entries in {}",
std::fs::read_to_string(source.join(relative)).unwrap(),
expected,
"relocated marketplace bytes differ at {relative}"
);
assert_eq!(
std::fs::read_to_string(cache.join(relative)).unwrap(),
expected,
"refreshed native cache bytes differ at {relative}"
);
}
for root in [&source, &cache] {
let unexpected = unexpected_bundle_entrypoints(
root,
&relatives,
&[".claude-plugin", "agents", "commands", "hooks", "skills"],
)
.unwrap();
assert!(
unexpected.is_empty(),
"refresh left auto-discovered entrypoints outside the catalog at {}: {unexpected:?}",
root.display()
);
}
assert!(claude_plugin_registration_is_active(home.path()).unwrap());
assert!(
claude_plugin_registration_is_active(home.path()).unwrap(),
"refresh must preserve the exact native plugin registration"
);
assert!(
claude_plugin_is_natively_active(home.path(), Some((new_bin, &ServeArgs::default())))
.unwrap()
.unwrap(),
"matching refreshed bytes must retain the exact discovery inventory"
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1200,14 +1200,21 @@ fn init_wait_holds_until_first_generation_is_ready() {
git(project.path(), &["init", "-b", "main"]);
commit_all(project.path(), "fixture repository");

let daemon_log = home.path().join("init-wait-daemon.log");
let _daemon =
crate::common::spawn_tracedecay_daemon_logged(home.path(), &daemon_log, |command| {
command.env("RUST_LOG", "info");
});
let mut command = tracedecay_command(home.path(), project.path());
command.args(["init", "--wait"]);
let output = run_with_timeout(command, cli_timeout());
assert!(
output.status.success(),
"init --wait should succeed once the first generation is ready\nstdout:\n{}\nstderr:\n{}",
"init --wait should succeed once the first generation is ready\nstdout:\n{}\nstderr:\n{}\ndaemon:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
String::from_utf8_lossy(&output.stderr),
std::fs::read_to_string(&daemon_log)
.unwrap_or_else(|error| format!("daemon log unavailable: {error}"))
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
Expand Down Expand Up @@ -3328,7 +3335,11 @@ fn branch_search_serves_a_committed_generation_behind_dirty_worktree_state() {
.unwrap();
init_project_fixture(home.path(), &project_root);

let _daemon = crate::common::spawn_tracedecay_daemon(home.path());
let daemon_log = home.path().join("dirty-branch-daemon.log");
let _daemon =
crate::common::spawn_tracedecay_daemon_logged(home.path(), &daemon_log, |command| {
command.env("RUST_LOG", "info");
});
let ready_deadline = Instant::now() + Duration::from_secs(30);
loop {
let mut ready = tracedecay_command_without_daemon(home.path(), &project_root);
Expand All @@ -3352,9 +3363,11 @@ fn branch_search_serves_a_committed_generation_behind_dirty_worktree_state() {
}
assert!(
Instant::now() < ready_deadline,
"dirty worktree generation did not become queryable\nstdout:\n{}\nstderr:\n{}",
"dirty worktree generation did not become queryable\nstdout:\n{}\nstderr:\n{}\ndaemon:\n{}",
String::from_utf8_lossy(&ready.stdout),
String::from_utf8_lossy(&ready.stderr)
String::from_utf8_lossy(&ready.stderr),
std::fs::read_to_string(&daemon_log)
.unwrap_or_else(|error| format!("daemon log unavailable: {error}"))
);
std::thread::sleep(Duration::from_millis(100));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,17 @@ fn expected_worker_reservation_on(runtime: &CodeIndexWorkerRuntimeV1, remaining_
)
}

/// Ledger scenarios own their measured input independently of sibling tests.
pub(super) fn ledger_memory(limit: NonZeroU64) -> Arc<ProcessResidentMemoryV1> {
Arc::new(ProcessResidentMemoryV1::with_pressure(
limit,
Arc::new(ResidentMemoryPressureV1::with_sampler(
limit,
Arc::new(|| None),
)),
))
}

#[test]
fn captured_source_bytes_are_charged_until_the_snapshot_drops() {
let project = fixture();
Expand All @@ -84,9 +95,7 @@ fn captured_source_bytes_are_charged_until_the_snapshot_drops() {
Arc::new(SharedCodeIndexBytePoolV1::default()),
)
.expect("open scheduler");
let authority = Arc::new(ProcessResidentMemoryV1::new(
NonZeroU64::new(1024 * 1024).expect("source memory limit"),
));
let authority = ledger_memory(NonZeroU64::new(1024 * 1024).expect("source memory limit"));
scheduler.bind_resident_memory(Arc::clone(&authority));

let captured = scheduler
Expand Down Expand Up @@ -119,9 +128,7 @@ fn reconcile_leaves_no_captured_source_charged_after_the_build() {
Arc::new(SharedCodeIndexBytePoolV1::default()),
)
.expect("open scheduler");
let authority = Arc::new(ProcessResidentMemoryV1::new(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
));
let authority = ledger_memory(DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1);
scheduler.bind_resident_memory(Arc::clone(&authority));
let captured = scheduler
.capture_authoritative_snapshot(None)
Expand Down Expand Up @@ -171,7 +178,7 @@ fn source_capture_refuses_before_build_when_retention_cannot_be_charged() {
Arc::new(SharedCodeIndexBytePoolV1::default()),
)
.expect("open scheduler");
let authority = Arc::new(ProcessResidentMemoryV1::new(NonZeroU64::MIN));
let authority = ledger_memory(NonZeroU64::MIN);
scheduler.bind_resident_memory(Arc::clone(&authority));

assert!(matches!(
Expand Down Expand Up @@ -291,9 +298,7 @@ fn worker_memory_reservation_is_charged_and_released_by_raii() {
2,
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1.get(),
);
let authority = Arc::new(ProcessResidentMemoryV1::new(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
));
let authority = ledger_memory(DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1);
scheduler.bind_resident_memory(Arc::clone(&authority));
let reservation = scheduler
.reserve_worker_memory()
Expand Down Expand Up @@ -325,14 +330,7 @@ fn default_authority_worker_reserve_leaves_typed_snapshot_headroom() {
let runtime = bind_automatic_worker_runtime(&scheduler, 128, 128 * 1024 * 1024 * 1024);
// This case isolates the ledger's worker/snapshot split. Live process
// pressure can narrow the slab and is exercised with explicit samples below.
let pressure = Arc::new(ResidentMemoryPressureV1::with_sampler(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
Arc::new(|| None),
));
let authority = Arc::new(ProcessResidentMemoryV1::with_pressure(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
pressure,
));
let authority = ledger_memory(DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1);
scheduler.bind_resident_memory(Arc::clone(&authority));

let _worker = scheduler
Expand Down Expand Up @@ -376,12 +374,12 @@ fn worker_memory_reservation_refusal_is_typed() {
2,
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1.get(),
);
let authority = Arc::new(ProcessResidentMemoryV1::new(
let authority = ledger_memory(
NonZeroU64::new(
tracedecay_code_index::parallelism::INDEX_WORKER_RESIDENT_BUDGET_BYTES_V1 - 1,
)
.expect("positive test limit"),
));
);
scheduler.bind_resident_memory(authority);
assert!(matches!(
scheduler.reserve_worker_memory(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ use tracedecay_domain::{
};
use tracedecay_graph_db::GraphConflictContextV1;
use tracedecay_privacy::CODE_SOURCE_SANITIZER_VERSION_V1;
#[cfg(any(test, feature = "test-helpers"))]
use tracedecay_runtime_core::resident_memory::ResidentMemoryPressureV1;
use tracedecay_runtime_core::resident_memory::{
ProcessResidentMemoryV1, RESIDENT_MEMORY_PRESSURE_ADMISSION_FLOOR_BYTES_V1,
RESIDENT_OWNER_IDLE_WINDOW_V1, ResidentMemoryAdmissionFailureV1, ResidentMemoryComponentIdV1,
Expand Down Expand Up @@ -1250,22 +1252,33 @@ impl HistoricalCodeIndexGenerationOwnerV1 {
}

impl CodeIndexWorktreeSchedulerV1 {
/// A standalone owner under the path policy a fresh profile resolves.
/// A standalone fixture owner under the path policy a fresh profile resolves.
/// Concurrent cases do not contribute measured pressure to this owner;
/// pressure scenarios bind their own explicit readings after opening it.
#[cfg(any(test, feature = "test-helpers"))]
pub fn open(
project_id: ProjectId,
project_root: &Path,
store_root: PathBuf,
byte_pool: Arc<SharedCodeIndexBytePoolV1>,
) -> Result<Self, CodeIndexSchedulerErrorV1> {
Self::open_with_policy(
let mut scheduler = Self::open_with_policy(
project_id,
project_root,
store_root,
byte_pool,
CodeIndexHintPolicyV1::default(),
crate::config::registry_default_index_path_policy(),
)
)?;
let limit = detected_process_resident_memory_limit_v1();
scheduler.bind_resident_memory(Arc::new(ProcessResidentMemoryV1::with_pressure(
limit,
Arc::new(ResidentMemoryPressureV1::with_sampler(
limit,
Arc::new(|| None),
)),
)));
Ok(scheduler)
}

pub fn open_with_policy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

use std::sync::Arc;

#[cfg(any(test, feature = "test-helpers"))]
use tracedecay_runtime_core::resident_memory::DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1;
pub use tracedecay_runtime_core::resident_memory::ProcessResidentMemoryV1;
#[cfg(any(test, feature = "test-helpers"))]
use tracedecay_runtime_core::resident_memory::RESIDENT_OWNER_IDLE_WINDOW_V1;
use tracedecay_runtime_core::resident_memory::{
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1, RESIDENT_OWNER_IDLE_WINDOW_V1,
ResidentMemoryPressureV1,
};
use tracedecay_runtime_core::resident_memory::{ResidentOwnersV1, process_resident_owners_v1};

use super::CodeIndexSchedulerRegistryV1;
Expand All @@ -15,10 +16,17 @@ impl CodeIndexSchedulerRegistryV1 {
#[cfg(any(test, feature = "test-helpers"))]
#[cfg_attr(not(test), allow(dead_code))]
pub fn new(max_worktrees: usize) -> Self {
// A fixture owns its ledger and pressure readings. Concurrent tests'
// RSS is not an input; pressure journeys pass an explicit authority.
let pressure = Arc::new(ResidentMemoryPressureV1::with_sampler(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
Arc::new(|| None),
));
Self::with_resident_memory_and_progress_producer_incarnation(
max_worktrees,
Arc::new(ProcessResidentMemoryV1::new(
Arc::new(ProcessResidentMemoryV1::with_pressure(
DEFAULT_PROCESS_RESIDENT_MEMORY_LIMIT_V1,
pressure,
)),
1,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1476,14 +1476,6 @@ fn ledger_leaving_one_mib(
.expect("the held memory fits the ledger")
}

fn sixteen_gib_authority() -> Arc<ProcessResidentMemoryV1> {
let limit = NonZeroU64::new(16 * 1024 * 1024 * 1024).unwrap();
Arc::new(ProcessResidentMemoryV1::with_pressure(
limit,
Arc::new(ResidentMemoryPressureV1::new(limit)),
))
}

/// A 16 GiB authority reading a fixed process size, so a held ledger refuses
/// a refresh however much memory the tests beside it hold.
fn fixed_reading_authority() -> Arc<ProcessResidentMemoryV1> {
Expand All @@ -1510,7 +1502,7 @@ async fn a_refresh_refused_for_memory_publishes_when_an_idle_owner_releases_it()
let fixture = GitFixture::new(&[("src/main.rs", "fn main() {}\n")]);
let store = TempDir::new().expect("store root");
let owners = Arc::new(ResidentOwnersV1::new(IDLE_WINDOW));
let resident_memory = sixteen_gib_authority();
let resident_memory = fixed_reading_authority();
let (registry, _) = mounted_core_query_worktree_in(
CodeIndexSchedulerRegistryV1::with_resident_memory(1, Arc::clone(&resident_memory))
.with_resident_owners(Arc::clone(&owners)),
Expand Down Expand Up @@ -1576,7 +1568,7 @@ async fn a_refresh_refused_by_the_ledger_publishes_when_the_blocking_reservation
let fixture = GitFixture::new(&[("src/main.rs", "fn main() {}\n")]);
let store = TempDir::new().expect("store root");
let owners = Arc::new(ResidentOwnersV1::new(IDLE_WINDOW));
let resident_memory = sixteen_gib_authority();
let resident_memory = fixed_reading_authority();
let (registry, _) = mounted_core_query_worktree_in(
CodeIndexSchedulerRegistryV1::with_resident_memory(1, Arc::clone(&resident_memory))
.with_resident_owners(Arc::clone(&owners)),
Expand Down
Loading
Loading