refactor(util): own the process-wide test-environment barrier so runtime can sit above util

`codewhale-util` carried an optional `codewhale-runtime` dependency behind its
`test-support` feature, purely so `util::utils` could enroll a spawned blocking
thread in the caller's sealed test environment. That prospective edge is what
blocked the S2 module moves: those modules need `codewhale-runtime` to depend on
`codewhale-util`, and util depending back on runtime is a cycle.

`test_env_lock` is 295 lines of `std` only — a `Mutex`, a `OnceLock` and thread
ids — so it moves down to `codewhale-util` unchanged and keeps one static for
every crate that uses it. `runtime::test_support` re-exports the same names, so
no caller outside `crates/util` changes. `util` loses the optional dependency;
its `test-support` feature remains as an empty switch, because the `cfg` in
`utils.rs` must still be enabled when another crate builds util as a dependency
(tui, plugins and skills all enable it). Each of those three already turns on
`codewhale-runtime/test-support` itself, so nothing relied on the transitive
edge being removed.

Verified: the workspace normal-dependency graph now has `runtime -> util` and no
path from `util` back to `runtime` (checked by walking every `[dependencies]`
table). `cargo check -p codewhale-util -p codewhale-runtime -p codewhale-snapshot
-p codewhale-notify --lib` and `--tests` for util and runtime pass under
RUSTFLAGS="-D warnings"; `codewhale-util --lib` runs 51 cases, 51 pass;
`codewhale-snapshot --lib first_init_waits_for_the_side_repo_write_lock`, which
uses `env_scope_ticket`/`join_env_scope` from the new location, passes, which is
what proves the static is shared rather than duplicated. The `Cargo.lock` diff is
one added line (`codewhale-util` under runtime).

Refs CW-6129
This commit is contained in:
CodeWhale Bot committed 2026-10-29 23:22:42 -07:00
1 parent e1c8c48d3b
commit 6dd9af54c6
7 files changed
+7 -7

No files matched your search

Generated
+1
View File
@@ -1369,6 +1369,7 @@ dependencies = [
"codewhale-protocol",
"codewhale-release",
"codewhale-secrets",
"codewhale-util",
"colored",
"dirs 7.0.0",
"fd-lock",
+1
View File
@@ -38,6 +38,7 @@ base64 = "0.23.1"
codewhale-cloud-facts = { path = "../cloud-facts", version = "0.10.2" }
codewhale-localization = { path = "../localization", version = "0.10.2" }
codewhale-protocol = { path = "../protocol", version = "0.10.2" }
codewhale-util = { path = "../util", version = "0.10.2" }
codewhale-release = { path = "../release", version = "0.10.2" }
codewhale-paths = { path = "../paths", version = "0.10.2" }
dirs.workspace = true
+1 -3
View File
@@ -6,9 +6,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};
use std::time::{SystemTime, UNIX_EPOCH};
mod test_env_lock;
pub use test_env_lock::{
pub use codewhale_util::test_env_lock::{
EnvScopeMembership, EnvScopeTicket, TestEnvLock, current_env_scope_generation,
current_thread_holds_test_env_lock, env_scope_ticket, join_env_scope, lock_test_env,
with_test_env_lock, with_test_env_lock_if_uncontended,
+1 -2
View File
@@ -11,7 +11,7 @@ description = "Codewhale shared utilities, confined workspace files, path identi
workspace = true
[features]
test-support = ["dep:codewhale-runtime", "codewhale-runtime/test-support"]
test-support = []
[dependencies]
anyhow.workspace = true
@@ -20,7 +20,6 @@ codewhale-config = { path = "../config", version = "0.10.2" }
codewhale-models = { path = "../models", version = "0.10.2" }
codewhale-paths = { path = "../paths", version = "0.10.2" }
codewhale-protocol = { path = "../protocol", version = "0.10.2" }
codewhale-runtime = { path = "../runtime", version = "0.10.2", optional = true }
codewhale-telemetry = { path = "../telemetry", version = "0.10.2" }
futures-util = "0.3.31"
ignore = "0.4"
+1
View File
@@ -4,5 +4,6 @@ pub mod fs_confined;
pub mod git_metadata;
pub mod lock;
pub mod path_identity;
pub mod test_env_lock;
pub mod utils;
pub mod workspace_dir;
+2 -2
View File
@@ -892,10 +892,10 @@ where
{
let location = std::panic::Location::caller();
#[cfg(any(test, feature = "test-support"))]
let env_ticket = codewhale_runtime::test_support::env_scope_ticket();
let env_ticket = crate::test_env_lock::env_scope_ticket();
tokio::task::spawn_blocking(move || {
#[cfg(any(test, feature = "test-support"))]
let _membership = codewhale_runtime::test_support::join_env_scope(env_ticket);
let _membership = crate::test_env_lock::join_env_scope(env_ticket);
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
if let Err(panic_info) = result {
let msg = panic_message(&*panic_info);