Skip to content

ci: add real Loom concurrency tests#6

Merged
bglusman merged 12 commits intomainfrom
pr/loom-tests
Apr 11, 2026
Merged

ci: add real Loom concurrency tests#6
bglusman merged 12 commits intomainfrom
pr/loom-tests

Conversation

@bglusman
Copy link
Copy Markdown
Owner

Replace placeholder loom CI job with actual concurrency tests using Loom for exhaustive thread interleaving exploration.

  • Add crates/loom-tests with 5 concurrency tests
  • Test patterns: registry access, session management, arc lifecycle, message passing, concurrent config access
  • Update CI to run with LOOM_MAX_PREEMPTIONS=2
  • Add loom-tests to workspace members

Refs: fix/loom-ci-real-tests (superseded due to rebase conflicts)

Description

Brief description of the changes in this PR.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Refactoring
  • CI/CD improvement

Testing

  • cargo test passes for all affected crates
  • cargo clippy passes with no warnings
  • cargo fmt is clean
  • New tests added for new functionality

Checklist

  • Code follows the project style guidelines
  • Self-review completed
  • Comments added for complex logic
  • Documentation updated (if applicable)
  • No new warnings introduced

Related Issues

Fixes # (issue number)

Breaking Changes

List any breaking changes and migration steps:

Additional Notes

Any additional context or screenshots.

Replace placeholder loom CI job with actual concurrency tests using
Loom for exhaustive thread interleaving exploration.

- Add crates/loom-tests with 5 concurrency tests
- Test patterns: registry access, session management, arc lifecycle,
  message passing, concurrent config access
- Update CI to run with LOOM_MAX_PREEMPTIONS=2
- Add loom-tests to workspace members

Refs: fix/loom-ci-real-tests (superseded due to rebase conflicts)
Copilot AI review requested due to automatic review settings April 11, 2026 15:03
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the previous no-op Loom CI job with an actual Loom test suite, adding a dedicated loom-tests crate to run concurrency model checks under cfg(loom) and wiring it into the workspace and CI.

Changes:

  • Add a new crates/loom-tests crate containing 5 Loom-based concurrency tests gated by cfg(loom).
  • Add loom-tests to the workspace and lockfile.
  • Update GitHub Actions CI to run Loom tests with RUSTFLAGS="--cfg loom" and LOOM_MAX_PREEMPTIONS=2.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
crates/loom-tests/src/lib.rs Introduces Loom model tests for several concurrency patterns.
crates/loom-tests/Cargo.toml Defines the new test crate and its Loom dependency/lint cfg.
Cargo.toml Adds crates/loom-tests to workspace members.
Cargo.lock Records the new workspace package entry.
.github/workflows/ci.yml Replaces the placeholder Loom job with a real Loom test run.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/loom-tests/src/lib.rs Outdated
t3.join().unwrap();

let guard = sessions_final.read().unwrap();
assert!(guard.len() >= 2);
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_concurrent_session_management's final assertion is too weak for the program order here: after joining t1 and t3, the map must contain all 3 inserted sessions (no removals and distinct keys). Using assert!(len >= 2) can let real regressions slip through; consider asserting the exact expected length and/or verifying the expected keys/values are present.

Suggested change
assert!(guard.len() >= 2);
assert_eq!(guard.len(), 3);
assert_eq!(guard.get("session_1"), Some(&"user_a".to_string()));
assert_eq!(guard.get("session_2"), Some(&"user_b".to_string()));
assert_eq!(guard.get("session_3"), Some(&"user_c".to_string()));

Copilot uses AI. Check for mistakes.
Comment thread crates/loom-tests/src/lib.rs Outdated
Comment on lines +24 to +33
/// Test concurrent access to a shared registry pattern
#[test]
fn test_concurrent_registry_access() {
loom::model(|| {
let registry: Arc<Mutex<HashMap<String, String>>> =
Arc::new(Mutex::new(HashMap::new()));
let registry2 = Arc::clone(&registry);
let registry_final = Arc::clone(&registry);

// Thread 1: Insert entries
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new loom-tests crate largely duplicates existing Loom tests already present in crates/zeroclawed/tests/loom.rs (registry/session/arc patterns). Keeping two copies will add maintenance overhead and can cause the CI Loom job to miss the more representative tests that exercise real project types (e.g., context_loom_test.rs). Consider consolidating (move/delete the old tests, or have this crate depend on and exercise the real types) so there is a single source of truth.

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/ci.yml Outdated
Comment on lines 89 to 90
# Loom concurrency tests (placeholder - no-op for now)
# ─────────────────────────────────────────────────────────────────────────────
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI header comment still says "placeholder - no-op for now", but this job now runs real Loom tests. Please update/remove the stale header to avoid misleading future maintainers.

Suggested change
# Loom concurrency tests (placeholder - no-op for now)
# ─────────────────────────────────────────────────────────────────────────────

Copilot uses AI. Check for mistakes.
Comment thread .github/workflows/ci.yml
key: ${{ runner.os }}-cargo-loom-${{ hashFiles('**/Cargo.lock') }}

- name: Run Loom tests
run: cargo test -p loom-tests
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Loom CI job currently only runs cargo test -p loom-tests. There are also existing Loom tests under crates/zeroclawed/tests/loom.rs and crates/zeroclawed/src/context_loom_test.rs gated by cfg(loom), which won't be exercised by this job. Consider extending the job to run those as well (or deleting/migrating them) so CI covers all Loom-gated concurrency tests in the repo.

Suggested change
run: cargo test -p loom-tests
run: |
cargo test -p loom-tests
cargo test -p zeroclawed --lib --tests

Copilot uses AI. Check for mistakes.
Comment thread crates/loom-tests/src/lib.rs Outdated
Comment on lines +51 to +52
assert_eq!(guard.get("agent1"), Some(&"config1".to_string()));
assert_eq!(guard.get("agent2"), Some(&"config2".to_string()));
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These assertions allocate new Strings just to compare against values stored in the map (e.g., Some(&"config1".to_string())). In a Loom model this extra allocation can increase the explored state space and slow the test. Prefer comparing via as_str()/string literals or direct equality on the borrowed String to avoid creating new heap allocations during the model run.

Suggested change
assert_eq!(guard.get("agent1"), Some(&"config1".to_string()));
assert_eq!(guard.get("agent2"), Some(&"config2".to_string()));
assert_eq!(guard.get("agent1").map(String::as_str), Some("config1"));
assert_eq!(guard.get("agent2").map(String::as_str), Some("config2"));

Copilot uses AI. Check for mistakes.
Librarian added 2 commits April 11, 2026 15:27
Address Copilot PR review feedback:
- Remove duplicate crates/loom-tests (tests already exist in zeroclawed)
- Update CI to run existing loom tests: cargo test -p zeroclawed --test loom
- Fix stale CI comment (was still saying 'placeholder - no-op')
- Improve test assertions per feedback:
  - Use exact length checks instead of >=
  - Use .map(String::as_str) to avoid allocations in assertions

Refs: PR #6
- Fix: --cfg loom breaks tokio::net, hyper-util, etc.
  Loom tests must live in a standalone crate without those deps.
- Fix: loom::Arc doesn't impl Copy - clone before each move
- Fix: loom::yield_now() → loom::thread::yield_now()
- Stronger assertions: exact counts + key/value verification
- Suppress unused import warnings (loom cfg aliases)
Copilot AI review requested due to automatic review settings April 11, 2026 15:35
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/loom-tests/Cargo.toml Outdated
Comment on lines +8 to +10

[dev-dependencies]
loom = "0.7"
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loom is declared in both [dependencies] and [dev-dependencies], which is redundant and can confuse dependency intent. Keep it in one section (for this crate, [dependencies] is sufficient since the tests live in src/lib.rs).

Suggested change
[dev-dependencies]
loom = "0.7"

Copilot uses AI. Check for mistakes.
Comment thread crates/loom-tests/src/lib.rs Outdated
//!
//! ```bash
//! RUSTFLAGS="--cfg loom" cargo test -p loom-tests
//! LOOM_MAX_PREEMPTIONS=3 RUSTFLAGS="--cfg loom" cargo test -p loom-tests
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module docs recommend LOOM_MAX_PREEMPTIONS=3, but CI runs Loom with LOOM_MAX_PREEMPTIONS=2. Please align the docs with the CI default (or update CI) so developers don’t get unexpected runtime differences when reproducing failures locally.

Suggested change
//! LOOM_MAX_PREEMPTIONS=3 RUSTFLAGS="--cfg loom" cargo test -p loom-tests
//! LOOM_MAX_PREEMPTIONS=2 RUSTFLAGS="--cfg loom" cargo test -p loom-tests

Copilot uses AI. Check for mistakes.
Comment thread crates/loom-tests/src/lib.rs Outdated
Comment on lines +13 to +17
#![allow(unexpected_cfgs)]
#![allow(unused_imports)]
#![allow(unused)]
#![cfg(loom)]

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crate is gated with #![cfg(loom)], so cargo test -p loom-tests without RUSTFLAGS="--cfg loom" will silently run 0 tests and still succeed. Consider adding a #[cfg(not(loom))] test that fails with instructions (or avoid crate-level gating and gate only the Loom-specific code) to prevent false confidence in local runs.

Copilot uses AI. Check for mistakes.
Comment thread crates/loom-tests/src/lib.rs Outdated
Comment on lines +13 to +16
#![allow(unexpected_cfgs)]
#![allow(unused_imports)]
#![allow(unused)]
#![cfg(loom)]
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#![allow(unused)] and #![allow(unused_imports)] are very broad and can hide real issues as these tests evolve. Prefer removing the blanket allows and only suppressing specific lints where they actually occur (keeping allow(unexpected_cfgs) if needed).

Copilot uses AI. Check for mistakes.
Comment thread crates/loom-tests/src/lib.rs Outdated
Comment on lines +23 to +29
// Silence warnings from cfg(loom) stripping
#[allow(unused_imports)]
#[allow(unused)]
mod _loom_hints {
use super::*;
}

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _loom_hints module appears to exist only to silence unused warnings, but this file already has crate-level allow(unused*). Once the blanket allows are removed, consider deleting _loom_hints unless there’s a concrete warning it addresses.

Suggested change
// Silence warnings from cfg(loom) stripping
#[allow(unused_imports)]
#[allow(unused)]
mod _loom_hints {
use super::*;
}

Copilot uses AI. Check for mistakes.
Comment thread crates/loom-tests/src/lib.rs Outdated
Comment on lines +30 to +35
/// Test concurrent access to a shared registry pattern
#[test]
fn test_concurrent_registry_access() {
loom::model(|| {
let registry = Arc::new(Mutex::new(HashMap::new()));
let r1 = Arc::clone(&registry);
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says this crate adds 5 concurrency tests, but loom-tests currently defines 6 #[test] functions. Please update the PR description (or remove/merge a test) so the change summary matches what’s being introduced.

Copilot uses AI. Check for mistakes.
Librarian added 2 commits April 11, 2026 15:39
Runs the same checks as CI before every push:
- cargo fmt --check (catches import ordering issues)
- cargo clippy --workspace --all-targets -D warnings
- Unit tests for all crates except loom-tests
- Loom tests in isolated crate (not -p zeroclawed --cfg loom)
- Workspace integrity checks

Flags:
  --quick     Skip loom and slow tests
  --loom-only Only run loom tests

Installs as .git/hooks/pre-push automatically.
Copilot AI review requested due to automatic review settings April 11, 2026 15:41
Why each check exists — prevents future regressions from the same
issues that caused CI failures in this branch.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/pre-push.sh
Comment on lines +83 to +86
# crate (loom-tests) with minimal deps — only `loom` itself.
#
# NEVER run: cargo test -p zeroclawed --cfg loom <-- BROKEN
# ALWAYS run: cargo test -p loom-tests with RUSTFLAGS="--cfg loom"
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This grep -rl is used only as a boolean check, but it will print matching filenames to stdout when it finds a match, adding noisy output to the pre-push script. Redirect stdout to /dev/null (or use grep -rq) so the check is silent and only the warn message is printed.

Copilot uses AI. Check for mistakes.
Comment thread scripts/pre-push.sh
Comment on lines +63 to +69
fail "clippy warnings"
fi
fi

# ─── Unit tests ─────────────────────────────────────────────────
if [ "$QUICK" = false ] && [ "$LOOM_ONLY" = false ]; then
echo "── Unit tests ─────────────────────────────────────────"
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says the new loom test crate adds 5 concurrency tests, but the script (and crate) currently expects 6. Please reconcile this by updating the PR description or adjusting the test set/count so documentation and tooling stay in sync.

Copilot uses AI. Check for mistakes.
Comment thread scripts/pre-push.sh
Comment on lines +91 to +96
fi
fi

# ─── Workspace integrity ───────────────────────────────────────
if [ "$LOOM_ONLY" = false ]; then
echo "── Workspace check ────────────────────────────────────"
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The summary path calls fail, which increments FAILURES as a side effect. Even though the script exits immediately after, this makes the counter inaccurate and is easy to trip over if the script later evolves (e.g., additional reporting after the summary). Print the summary message without mutating FAILURES (separate helper or plain echo).

Copilot uses AI. Check for mistakes.
Librarian added 4 commits April 11, 2026 16:03
Copilot feedback addressed:
- Remove duplicate loom from [dev-dependencies] (only needed in [deps])
- Strip blanket #![allow(unused)] — keep only unexpected_cfgs
- Remove dead _loom_hints module
- Align docs with CI: MAX_PREEMPTIONS=2 not 3
- Fix grep -rl -> -rq in pre-push.sh (suppress noisy filenames)
- Cargo fmt on adapter_edge_cases.rs and security_tests.rs

Adapter edge cases rewritten with real tests:
- Binary not found, timeout propagation, shell safety, ACPX kind
- Instance isolation, onecli config defaults, dispatch error paths
- Added stream timeout test, PATH injection test, env passthrough test

Security tests rewritten with real assertions:
- AdapterError display leak checks (6 error variants)
- ResolvedIdentity struct field validation
- Empty/invalid sender sender authorization
- Outbound message pattern detection
- Config default fail-closed behavior
- Overflow/edge-case string handling
- Add #[cfg(not(loom))] guard test that fails with clear instructions
  when RUSTFLAGS='--cfg loom' is missing (prevents silent 0-test runs)
- Restructure: crate-level #![cfg(loom)] → per-module #[cfg(loom)]
- Fix pre-push summary to echo instead of mutating FAILURES counter
- Run cargo fmt on e2e tests
The guard test test_loom_cfg_missing panics when --cfg loom is missing.
The integration-tests.yml runs cargo test --workspace without RUSTFLAGS,
so it picks up and fails the guard. loom-tests must only run via the
dedicated ci.yml loom job with RUSTFLAGS='--cfg loom'.
…om file

- integration-tests.yml: --exclude loom-tests so guard test doesn't panic
- Delete dead crates/zeroclawed/src/context_loom_test.rs (never included
  in any mod; loom tests live in crates/loom-tests/)
- Run cargo fmt on adapter_edge_cases.rs and security_tests.rs
Copilot AI review requested due to automatic review settings April 11, 2026 16:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to +25
/// Helper: spawn a command with optional env and timeout, return stdout+stderr
fn run_cmd(
cmd: &str,
args: &[&str],
env: Option<HashMap<String, String>>,
timeout_ms: u64,
) -> Result<String, String> {
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The run_cmd doc comment says it returns "stdout+stderr", but the function actually returns only stdout on success and only stderr on non-zero exit (and a custom string on spawn/timeout). Update the comment (or the return type) so callers/tests know exactly what’s returned.

Copilot uses AI. Check for mistakes.
Comment on lines +186 to 191
// Verify we can't inject PATH to change binary behavior
// (This tests that the command is resolved before PATH changes take effect)
let result = run_cmd("echo", &["safe"], None, 5000);
assert!(result.is_ok());
assert!(result.unwrap().contains("safe"));
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_path_not_injected claims to verify PATH injection behavior, but it never mutates PATH (or otherwise attempts any injection), so the test doesn’t exercise what the comments describe. Either adjust the test to actually change PATH and validate behavior, or rename/reword the test to match what it asserts.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +34
if let Err(e) = result {
let msg = format!("{e}");
// OS error "No such file or directory" is fine — it's user-facing
// But should NOT contain /root, /etc, or token-like strings
assert!(!msg.contains("/root"), "Error should not leak /root: {msg}");
assert!(!msg.contains("/etc"), "Error should not leak /etc: {msg}");
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only asserts on the Err branch; if spawn() unexpectedly succeeds (e.g., a binary with this name exists in PATH), the test becomes a no-op and will pass without checking anything. Make the test assert that spawning fails (or otherwise force an error condition) so it can’t pass vacuously.

Suggested change
if let Err(e) = result {
let msg = format!("{e}");
// OS error "No such file or directory" is fine — it's user-facing
// But should NOT contain /root, /etc, or token-like strings
assert!(!msg.contains("/root"), "Error should not leak /root: {msg}");
assert!(!msg.contains("/etc"), "Error should not leak /etc: {msg}");
}
let e = result.expect_err("spawning nonexistent_bin_xyz should fail");
let msg = format!("{e}");
// OS error "No such file or directory" is fine — it's user-facing
// But should NOT contain /root, /etc, or token-like strings
assert!(!msg.contains("/root"), "Error should not leak /root: {msg}");
assert!(!msg.contains("/etc"), "Error should not leak /etc: {msg}");

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +67
/// Test concurrent access to a shared registry pattern
#[test]
fn test_concurrent_registry_access() {
loom::model(|| {
let registry = Arc::new(Mutex::new(HashMap::new()));
let r1 = Arc::clone(&registry);
let r2 = Arc::clone(&registry);

// Thread 1: Insert entries
let t1 = thread::spawn(move || {
let mut guard = r1.lock().unwrap();
guard.insert("agent1".to_string(), "config1".to_string());
guard.insert("agent2".to_string(), "config2".to_string());
});

// Thread 2: Read and verify entries
let t2 = thread::spawn(move || {
let guard = r2.lock().unwrap();
let _count = guard.len();
});

t1.join().unwrap();
t2.join().unwrap();

// Verify final state
let guard = registry.lock().unwrap();
assert_eq!(guard.len(), 2);
assert_eq!(guard.get("agent1").map(String::as_str), Some("config1"));
assert_eq!(guard.get("agent2").map(String::as_str), Some("config2"));
});
}

/// Test session management pattern with concurrent reads and writes
#[test]
fn test_concurrent_session_management() {
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says the new crates/loom-tests crate adds 5 Loom tests, but this file defines 6 #[test] cases under #[cfg(loom)]. Either update the PR description to match, or drop/merge a test so the count/patterns described are accurate.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +8
// Integration tests for ZeroClawed + OneCLI
// Compiled as: cargo test --test e2e

pub mod adapter_edge_cases;
pub mod config_sanity;
pub mod onecli_proxy;
pub mod property_tests;
pub mod security_tests;
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description focuses on adding Loom CI/tests, but this change also rewires the e2e test entrypoint (mod.rs → main.rs) and adds new self-contained subprocess-based adapter/security test suites. Please update the PR description (or split the PR) so reviewers/CI stakeholders know these e2e changes are included.

Copilot uses AI. Check for mistakes.
Librarian added 2 commits April 11, 2026 16:37
Deleted 20 trivial tests that always passed:
- 7 test_kind_is_* tests (verified a hardcoded getter)
- 8 test_default_timeout/test_custom_timeout (verified constructor arg storage)
- 3 test_env_vars_set/test_default_args_when_none/test_default_timeout_is_*
- 1 test_config_file_location_precedence (print-only, no assertions)
- 2 tautological property tests (test_timeout_values, test_credential_isolation)

Rewrote weak tests:
- config_sanity: deleted no-assertion and tautological tests, added
  test_nzc_native_without_command and test_empty_agents_array_valid
- property_tests: deleted tautologies (config_parsing_deterministic,
  adapter_kind_validation, credential_isolation, timeout_values),
  added real properties (adapter_kind_exhaustive, phone_normalization_*)
  with proper exhaustive assertions

Added real tests to acpx.rs:
- 12 unit tests for strip_acpx_noise() (protocol line filtering)
- 2 unit tests for new() (default args, custom args)

Remaining tests: 345 unit + 33 e2e + 6 loom, all passing.
All tests now assert on actual behavior or verify non-trivial properties.
Consider Surelock (seanmonstar/surelock) for future mutex patterns.
- crates/loom-tests/docs/concurrency-testing.md
- Link from loom-tests lib.rs module doc
@bglusman bglusman merged commit 0dc9f05 into main Apr 11, 2026
13 checks passed
bglusman added a commit that referenced this pull request Apr 24, 2026
Security-proxy now shares onecli-client's vault resolver rather than
its own parallel startup-time env scan. Per-request, on cache miss,
it consults env → fnox → vaultwarden and caches the result. This
fixes the silently-broken rotation behaviour documented in
`docs/rfcs/consolidation-findings.md` finding #5: previously, a
rotated key added to env or fnox after security-proxy started was
invisible until restart.

Changes:
- Add `onecli-client = { path = "../onecli-client" }` as a path dep.
- New `CredentialInjector::ensure_cached(provider) -> bool` that
  returns fast when the DashMap already has the provider, else calls
  `onecli_client::vault::get_secret(provider)` and inserts on success.
- Public wrapper `detect_provider_pub(host)` so the proxy hot path can
  determine the provider name without duplicating the pattern table.
- Proxy.rs resolves + caches before calling inject() on each request.
- Two behavior tests covering cache-hit (no resolver call) and
  nothing-resolves (cache stays clean, no `Bearer ""` footgun).
  Written in given/when/then doc form as per today's test-quality
  review discussion.

Left for follow-up commits on this branch:
- Merge onecli-client's `/vault/:secret` + `/proxy/:provider` routes
  into security-proxy (move the handlers, then delete main.rs).
- Reconcile env-var conventions (ZEROGATE_KEY_* vs <NAME>_API_KEY —
  finding #1).
- Per-provider auth header schemes (finding #3 — onecli only does
  Bearer; security-proxy has anthropic's x-api-key path).
- Remove hardcoded `vault.enjyn.com` fallback (finding #6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bglusman added a commit that referenced this pull request Apr 24, 2026
…#6)

Per CLAUDE.md "Hard-coded fallback URLs": env vars for infra endpoints
should be mandatory, not silently defaulted to a specific host. The
prior default leaked a deployment-specific domain to anyone reading
the repo and would silently hit the wrong vault server when users
forgot to set the env.

Also: previously the function only bailed when the token was empty —
if the user set ONECLI_VAULT_TOKEN but forgot ONECLI_VAULT_URL, we'd
try to GET `/api/ciphers` against an empty base, producing a confusing
reqwest error. Now both are required; the bail message names exactly
which one is missing.

Existing tests still pass (24 in onecli-client lib). Note that
`fnox_failure_falls_through_to_vault_error` on feat/fnox-integration
asserts against the old "No ONECLI_VAULT_TOKEN" substring; its
alternative-phrase matcher ("not found") covers the new message, so
that test should still pass after merge. Worth verifying when the
branches reconcile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bglusman added a commit that referenced this pull request Apr 25, 2026
* ci: add real Loom concurrency tests

Replace placeholder loom CI job with actual concurrency tests using
Loom for exhaustive thread interleaving exploration.

- Add crates/loom-tests with 5 concurrency tests
- Test patterns: registry access, session management, arc lifecycle,
  message passing, concurrent config access
- Update CI to run with LOOM_MAX_PREEMPTIONS=2
- Add loom-tests to workspace members

Refs: fix/loom-ci-real-tests (superseded due to rebase conflicts)

* ci: fix Loom tests - use existing tests, remove duplicate crate

Address Copilot PR review feedback:
- Remove duplicate crates/loom-tests (tests already exist in zeroclawed)
- Update CI to run existing loom tests: cargo test -p zeroclawed --test loom
- Fix stale CI comment (was still saying 'placeholder - no-op')
- Improve test assertions per feedback:
  - Use exact length checks instead of >=
  - Use .map(String::as_str) to avoid allocations in assertions

Refs: PR #6

* fix: isolate loom tests in separate crate

- Fix: --cfg loom breaks tokio::net, hyper-util, etc.
  Loom tests must live in a standalone crate without those deps.
- Fix: loom::Arc doesn't impl Copy - clone before each move
- Fix: loom::yield_now() → loom::thread::yield_now()
- Stronger assertions: exact counts + key/value verification
- Suppress unused import warnings (loom cfg aliases)

* fix(fmt): sort imports in loom-tests

* ci: add local pre-push checks script

Runs the same checks as CI before every push:
- cargo fmt --check (catches import ordering issues)
- cargo clippy --workspace --all-targets -D warnings
- Unit tests for all crates except loom-tests
- Loom tests in isolated crate (not -p zeroclawed --cfg loom)
- Workspace integrity checks

Flags:
  --quick     Skip loom and slow tests
  --loom-only Only run loom tests

Installs as .git/hooks/pre-push automatically.

* docs(pre-push): document lessons learned in comments

Why each check exists — prevents future regressions from the same
issues that caused CI failures in this branch.

* Address Copilot review: loom-tests cleanup + real e2e tests

Copilot feedback addressed:
- Remove duplicate loom from [dev-dependencies] (only needed in [deps])
- Strip blanket #![allow(unused)] — keep only unexpected_cfgs
- Remove dead _loom_hints module
- Align docs with CI: MAX_PREEMPTIONS=2 not 3
- Fix grep -rl -> -rq in pre-push.sh (suppress noisy filenames)
- Cargo fmt on adapter_edge_cases.rs and security_tests.rs

Adapter edge cases rewritten with real tests:
- Binary not found, timeout propagation, shell safety, ACPX kind
- Instance isolation, onecli config defaults, dispatch error paths
- Added stream timeout test, PATH injection test, env passthrough test

Security tests rewritten with real assertions:
- AdapterError display leak checks (6 error variants)
- ResolvedIdentity struct field validation
- Empty/invalid sender sender authorization
- Outbound message pattern detection
- Config default fail-closed behavior
- Overflow/edge-case string handling

* Address Copilot review: loom guard test + pre-push cleanup

- Add #[cfg(not(loom))] guard test that fails with clear instructions
  when RUSTFLAGS='--cfg loom' is missing (prevents silent 0-test runs)
- Restructure: crate-level #![cfg(loom)] → per-module #[cfg(loom)]
- Fix pre-push summary to echo instead of mutating FAILURES counter
- Run cargo fmt on e2e tests

* fix(ci): exclude loom-tests from integration workflow

The guard test test_loom_cfg_missing panics when --cfg loom is missing.
The integration-tests.yml runs cargo test --workspace without RUSTFLAGS,
so it picks up and fails the guard. loom-tests must only run via the
dedicated ci.yml loom job with RUSTFLAGS='--cfg loom'.

* fix(ci): exclude loom-tests from integration workflow, remove dead loom file

- integration-tests.yml: --exclude loom-tests so guard test doesn't panic
- Delete dead crates/zeroclawed/src/context_loom_test.rs (never included
  in any mod; loom tests live in crates/loom-tests/)
- Run cargo fmt on adapter_edge_cases.rs and security_tests.rs

* chore: remove low-quality tests, add acpx tests, rewrite property tests

Deleted 20 trivial tests that always passed:
- 7 test_kind_is_* tests (verified a hardcoded getter)
- 8 test_default_timeout/test_custom_timeout (verified constructor arg storage)
- 3 test_env_vars_set/test_default_args_when_none/test_default_timeout_is_*
- 1 test_config_file_location_precedence (print-only, no assertions)
- 2 tautological property tests (test_timeout_values, test_credential_isolation)

Rewrote weak tests:
- config_sanity: deleted no-assertion and tautological tests, added
  test_nzc_native_without_command and test_empty_agents_array_valid
- property_tests: deleted tautologies (config_parsing_deterministic,
  adapter_kind_validation, credential_isolation, timeout_values),
  added real properties (adapter_kind_exhaustive, phone_normalization_*)
  with proper exhaustive assertions

Added real tests to acpx.rs:
- 12 unit tests for strip_acpx_noise() (protocol line filtering)
- 2 unit tests for new() (default args, custom args)

Remaining tests: 345 unit + 33 e2e + 6 loom, all passing.
All tests now assert on actual behavior or verify non-trivial properties.
Consider Surelock (seanmonstar/surelock) for future mutex patterns.

* docs: add Surelock concurrency testing design doc

- crates/loom-tests/docs/concurrency-testing.md
- Link from loom-tests lib.rs module doc

---------

Co-authored-by: Librarian <librarian@glusman.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants