From baba7bdb19af5dd6b12bde1c659b718d8274c5f5 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 13:04:59 -0700 Subject: [PATCH 01/29] fix(sessions): converge skipped and rewritten source histories Paginate unoffered source records and accept empty refresh output when canonical effects support the bound frontier. Cover skipped refreshes across reopen and keep unsupported frontier advancement refused. Integrates the production fixes contributed in PR #3185. Fixes #3183. Fixes #3199. Co-authored-by: spa5k <79936503+spa5k@users.noreply.github.com> --- .../src/registered_source_rewrite.rs | 78 +++++--- crates/tracedecay-global-db/src/tests.rs | 2 + .../src/tests/source_rewrite.rs | 88 +++++++++ .../src/projection/tests.rs | 179 ++++++++++++++++++ .../src/rebuild.rs | 47 ++++- 5 files changed, 367 insertions(+), 27 deletions(-) create mode 100644 crates/tracedecay-global-db/src/tests/source_rewrite.rs diff --git a/crates/tracedecay-global-db/src/registered_source_rewrite.rs b/crates/tracedecay-global-db/src/registered_source_rewrite.rs index b59ddf4ecb..2393bc63e3 100644 --- a/crates/tracedecay-global-db/src/registered_source_rewrite.rs +++ b/crates/tracedecay-global-db/src/registered_source_rewrite.rs @@ -15,6 +15,12 @@ use tracedecay_store::{ProjectionStoreError, ProjectionStoreResult}; use super::RegisteredGlobalDb; use super::observation_projection::{ensure_projection_output_state_cache, retire_source_record}; +/// Unoffered-record reads are keyset-paginated at this page size. The exact- +/// SQL engine refuses a query that materializes more rows than its hard cap, +/// and one long host session can hold more unoffered records than that cap, +/// which made rewrite completion fail deterministically for those sources. +const UNOFFERED_RECORD_PAGE_SIZE: i64 = 1_000; + /// One record a source layout offered, under the identity admission derived. pub struct ObservationSourcePresenceV1 { pub observation_id: CanonicalObservationIdV1, @@ -207,31 +213,57 @@ impl RegisteredGlobalDb { .map_err(|error| storage("release source rewrite completion", error))?; return Ok(0); } - let mut rows = transaction - .query( - "SELECT observation_id FROM observation_source_presence - WHERE source_key = ?1 AND generation <> ?2", - params![key.as_str(), generation.as_str()], - ) - .await - .map_err(|error| storage("read unoffered source records", error))?; - let mut unoffered = Vec::new(); - while let Some(row) = rows - .next() - .await - .map_err(|error| storage("read unoffered source records", error))? - { - unoffered.push( - row.get::(0) - .map_err(|error| storage("read unoffered source records", error))?, - ); - } - drop(rows); ensure_projection_output_state_cache(&transaction).await?; let mut retired = 0_u64; - for observation_id in &unoffered { - if retire_source_record(&transaction, observation_id).await? { - retired += 1; + // Keyset-paginate the unoffered ids instead of materializing the whole + // set in one statement: a single rewritten source can hold more + // unoffered records than the engine's per-query materialization cap, + // and one oversized read wedged every completion attempt forever. + // `retire_source_record` never touches `observation_source_presence`, + // so the ordered id cursor stays stable across pages; the settle + // deletes below remain the authoritative cleanup. + let mut after_observation_id = String::new(); + loop { + let mut rows = transaction + .query( + "SELECT observation_id FROM observation_source_presence + WHERE source_key = ?1 AND generation <> ?2 + AND observation_id > ?3 + ORDER BY observation_id + LIMIT ?4", + params![ + key.as_str(), + generation.as_str(), + after_observation_id, + UNOFFERED_RECORD_PAGE_SIZE + ], + ) + .await + .map_err(|error| storage("read unoffered source records", error))?; + let mut page = Vec::new(); + while let Some(row) = rows + .next() + .await + .map_err(|error| storage("read unoffered source records", error))? + { + page.push( + row.get::(0) + .map_err(|error| storage("read unoffered source records", error))?, + ); + } + drop(rows); + if page.is_empty() { + break; + } + let page_len = page.len(); + after_observation_id = page[page_len - 1].clone(); + for observation_id in &page { + if retire_source_record(&transaction, observation_id).await? { + retired += 1; + } + } + if (page_len as i64) < UNOFFERED_RECORD_PAGE_SIZE { + break; } } for sql in [ diff --git a/crates/tracedecay-global-db/src/tests.rs b/crates/tracedecay-global-db/src/tests.rs index dc9488ff86..f9602f7467 100644 --- a/crates/tracedecay-global-db/src/tests.rs +++ b/crates/tracedecay-global-db/src/tests.rs @@ -17,6 +17,8 @@ mod payload_gc_cost; mod schema_install_cancellation; #[cfg(test)] mod session_sync; +#[cfg(test)] +mod source_rewrite; #[cfg(test)] use super::registered_analytics::ANALYTICS_INSERT_ROWS_PER_STATEMENT; diff --git a/crates/tracedecay-global-db/src/tests/source_rewrite.rs b/crates/tracedecay-global-db/src/tests/source_rewrite.rs new file mode 100644 index 0000000000..4128881a6e --- /dev/null +++ b/crates/tracedecay-global-db/src/tests/source_rewrite.rs @@ -0,0 +1,88 @@ +//! Regression tests for registered source-rewrite completion. +//! +//! Rewrite completion retires every record the rewritten source no longer +//! offers. One long host session can hold more unoffered records than the +//! exact-SQL engine's per-query materialization cap, so completion must read +//! them in keyset pages rather than one unbounded statement; the unbounded +//! read failed deterministically for exactly those sources and wedged the +//! host catch-up in a retry loop that never converged. + +use tracedecay_domain::{ + ObservationScopeV1, ObservationSourceGenerationV1, ObservationSourceIdentityV1, SessionId, +}; +use tracedecay_runtime_core::db::engine::params; + +use crate::tests::harness::RegisteredGlobalDbHarness; + +/// More unoffered records than one keyset page *and* more than the engine's +/// per-query materialization cap, so the test fails against the unbounded +/// read and exercises repeated pagination against the fix. +const UNOFFERED_RECORDS: i64 = 10_003; + +#[tokio::test] +async fn rewrite_completion_retires_unoffered_records_beyond_the_query_cap() { + let harness = RegisteredGlobalDbHarness::open("source-rewrite-unoffered-cap").await; + let source = + ObservationSourceIdentityV1::new(SessionId::new("session.rewrite-cap").unwrap()).unwrap(); + let scope = ObservationScopeV1::Profile; + let previous = ObservationSourceGenerationV1::new(1).unwrap(); + let generation = ObservationSourceGenerationV1::new(2).unwrap(); + + let key = serde_json::to_string(&(source.clone(), scope.clone())).unwrap(); + let writer = harness.registered.writer_connection().unwrap(); + for index in 0..UNOFFERED_RECORDS { + writer + .execute( + "INSERT INTO observation_source_presence ( + source_key, observation_id, generation, start_offset + ) VALUES (?1, ?2, ?3, 0)", + params![ + key.as_str(), + format!("sha256:unoffered-{index:06}"), + previous.generation_id().to_string() + ], + ) + .await + .unwrap(); + } + + harness + .registered + .begin_observation_source_rewrite(&source, &scope, previous, generation.clone(), 0) + .await + .unwrap(); + + // None of the seeded records are re-offered under the new generation, so + // completion must retire-path all of them. The `observations` table is + // empty, so each retire is a no-op returning false; what matters is that + // the reads stay under the materialization cap and the settle deletes + // still clear the source. + let retired = harness + .registered + .complete_observation_source_rewrite(&source, &scope, generation) + .await + .unwrap_or_else(|error| panic!("rewrite completion must not exceed query caps: {error}")); + assert_eq!(retired, 0); + + let mut remaining = writer + .query( + "SELECT COUNT(*) FROM observation_source_presence WHERE source_key = ?1", + params![key.as_str()], + ) + .await + .unwrap(); + let count = remaining.next().await.unwrap().unwrap(); + let presence: i64 = count.get(0).unwrap(); + assert_eq!(presence, 0, "unoffered presence rows must be settled"); + + let mut pending = writer + .query( + "SELECT COUNT(*) FROM observation_source_rewrites WHERE source_key = ?1", + params![key.as_str()], + ) + .await + .unwrap(); + let row = pending.next().await.unwrap().unwrap(); + let rewrites: i64 = row.get(0).unwrap(); + assert_eq!(rewrites, 0, "completed rewrite row must be removed"); +} diff --git a/crates/tracedecay-session-temporal-store/src/projection/tests.rs b/crates/tracedecay-session-temporal-store/src/projection/tests.rs index ce07d4334f..71185aa708 100644 --- a/crates/tracedecay-session-temporal-store/src/projection/tests.rs +++ b/crates/tracedecay-session-temporal-store/src/projection/tests.rs @@ -467,6 +467,185 @@ async fn relation_batch_persists_restarts_and_completes_without_duplicates() { } } +#[tokio::test] +async fn all_skipped_refresh_persists_reopens_and_completes_idempotently() { + let tmp = TempDir::new().unwrap(); + let session_id = fixture_session("session.projector.all-skipped"); + let request; + { + let runtime = HostAdmissionTestRuntimeV1::profile(tmp.path()) + .await + .unwrap(); + let (observation, write) = fixture_observation(&session_id, 0, None, false); + Box::pin(persist_fixture(&runtime, observation, write)).await; + let store = temporal_store(&runtime); + refresh_through(&store, &session_id, 1, 0).await; + for ordinal in 1..=2 { + let (observation, write) = fixture_observation_from_facts( + &session_id, + ordinal, + ProviderId::new(format!("projector-test-{ordinal}")).unwrap(), + ObservationId::new(format!("record.projector.{ordinal}")).unwrap(), + CanonicalObservationRelationsV1::new(session_id.clone()), + vec![CanonicalObservationFactV1::Git { + evidence_kind: tracedecay_domain::CanonicalGitEvidenceKindV1::Branch, + reference: Some("refs/heads/main".to_owned()), + content: None, + }], + None, + ); + assert_eq!( + tracedecay_store::derive_canonical_projection(&observation) + .unwrap() + .skip_reason(), + Some(ProjectionSkipReason::NonConversationalRecord) + ); + Box::pin(persist_fixture(&runtime, observation, write)).await; + } + store + .begin_or_join_session_refresh(SessionRefreshBeginOrJoinRequestV1::new( + session_id.clone(), + SessionRefreshFrontierV1::new(3, 1).unwrap(), + )) + .await + .unwrap(); + let recovery = store + .session_refresh_recovery(&session_id) + .await + .unwrap() + .unwrap(); + let (progress, batch) = store + .materialize_session_temporal_refresh_batch_for_test(&recovery) + .await + .unwrap() + .unwrap(); + assert!(batch.occurrences().is_empty()); + assert_eq!(batch.item_count(), 0); + assert_eq!(progress.frontier().committed_through(), 3); + request = SessionRefreshCompletionRequestV1::new( + progress.operation_id().clone(), + session_id.clone(), + progress.frontier(), + *progress.coverage(), + ) + .unwrap(); + store + .persist_session_refresh_projection_batch(progress, batch) + .await + .unwrap(); + } + let receipt = { + let runtime = HostAdmissionTestRuntimeV1::profile(tmp.path()) + .await + .unwrap(); + let store = temporal_store(&runtime); + let recovery = store + .session_refresh_recovery(&session_id) + .await + .unwrap() + .unwrap(); + assert_eq!( + recovery.restart_state(), + SessionRefreshRestartStateV1::ReadyToComplete + ); + let receipt = store + .complete_session_refresh(request.clone(), ExecutionControl::default()) + .await + .unwrap(); + assert_eq!(receipt.state(), SessionRefreshTerminalStateV1::Complete); + receipt + }; + let runtime = HostAdmissionTestRuntimeV1::profile(tmp.path()) + .await + .unwrap(); + assert_eq!( + temporal_store(&runtime) + .complete_session_refresh(request, ExecutionControl::default()) + .await + .unwrap(), + receipt + ); + for (kind, expected) in [ + (SessionTemporalFixtureCountV1::Occurrences, 1), + (SessionTemporalFixtureCountV1::RefreshReceipts, 2), + ] { + assert_eq!( + runtime + .session_temporal_fixture_count_for_test(HostAdmissionScope::Profile, kind) + .await + .unwrap(), + expected + ); + } +} + +#[tokio::test] +async fn refresh_frontier_without_observation_effects_cannot_activate() { + let tmp = TempDir::new().unwrap(); + let runtime = HostAdmissionTestRuntimeV1::profile(tmp.path()) + .await + .unwrap(); + let session_id = fixture_session("session.projector.unsupported-frontier"); + let (observation, write) = fixture_observation(&session_id, 0, None, false); + Box::pin(persist_fixture(&runtime, observation, write)).await; + let store = temporal_store(&runtime); + refresh_through(&store, &session_id, 1, 0).await; + store + .begin_or_join_session_refresh(SessionRefreshBeginOrJoinRequestV1::new( + session_id.clone(), + SessionRefreshFrontierV1::new(2, 1).unwrap(), + )) + .await + .unwrap(); + let recovery = store + .session_refresh_recovery(&session_id) + .await + .unwrap() + .unwrap(); + let (progress, batch) = store + .materialize_session_temporal_refresh_batch_for_test(&recovery) + .await + .unwrap() + .unwrap(); + assert!(batch.occurrences().is_empty()); + assert_eq!(progress.frontier().committed_through(), 2); + let request = SessionRefreshCompletionRequestV1::new( + progress.operation_id().clone(), + session_id.clone(), + progress.frontier(), + *progress.coverage(), + ) + .unwrap(); + store + .persist_session_refresh_projection_batch(progress, batch) + .await + .unwrap(); + let error = store + .complete_session_refresh(request, ExecutionControl::default()) + .await + .expect_err("an empty batch cannot prove an unsupported source frontier"); + assert!(matches!(error, SessionStoreError::Storage { .. })); + assert_eq!( + store + .session_refresh_recovery(&session_id) + .await + .unwrap() + .unwrap() + .restart_state(), + SessionRefreshRestartStateV1::ReadyToComplete + ); + assert_eq!( + runtime + .session_temporal_fixture_count_for_test( + HostAdmissionScope::Profile, + SessionTemporalFixtureCountV1::RefreshReceipts + ) + .await + .unwrap(), + 1 + ); +} + #[tokio::test] async fn terminal_receipt_rejects_corrupted_derived_evidence() { let tmp = TempDir::new().unwrap(); diff --git a/crates/tracedecay-session-temporal-store/src/rebuild.rs b/crates/tracedecay-session-temporal-store/src/rebuild.rs index 3f1652516b..aac5d91612 100644 --- a/crates/tracedecay-session-temporal-store/src/rebuild.rs +++ b/crates/tracedecay-session-temporal-store/src/rebuild.rs @@ -233,10 +233,49 @@ pub(super) async fn validate_candidate_frontier( expected.insert(occurrence_id.clone()); } if expected.is_empty() && source_frontier != base_frontier { - return Err(storage_message( - ACTIVATE_OPERATION, - "candidate generation has no canonical message outputs past its base frontier", - )); + // A frontier advance whose every observation was deterministically + // skipped (effects recorded with zero outputs, e.g. host + // non-conversational records) legitimately introduces no canonical + // outputs. Refusing those activations wedged the refresh worker in a + // permanent retry loop. Only an advance that processed no + // observation at all is a validation failure. + let mut processed_any = conn + .query( + "SELECT EXISTS ( + SELECT 1 FROM session_temporal_observation_effects + WHERE session_id = ?1 + AND observation_sequence > ?2 + AND observation_sequence <= ?3 + )", + params![ + session_id, + i64::try_from(base_frontier) + .map_err(|error| storage(ACTIVATE_OPERATION, error))?, + i64::try_from(source_frontier) + .map_err(|error| storage(ACTIVATE_OPERATION, error))? + ], + ) + .await + .map_err(|error| storage(ACTIVATE_OPERATION, error))?; + let processed = match processed_any + .next() + .await + .map_err(|error| storage(ACTIVATE_OPERATION, error))? + { + Some(row) => { + row.get::(0) + .map_err(|error| storage(ACTIVATE_OPERATION, error))? + != 0 + } + None => false, + }; + drop(processed_any); + if !processed { + return Err(storage_message( + ACTIVATE_OPERATION, + "candidate generation advanced its source frontier without processing any observations", + )); + } } require_unsettled_message_ids(conn, session_id, generation, &parent_resolver).await?; From 0226587de676b0e89c9aef0becdd4e11e4b13232 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 13:05:27 -0700 Subject: [PATCH 02/29] build(bazel): bound Windows compiler wrapper arguments Fixes #3186. --- MODULE.bazel | 7 ++++- .../rules-rust-windows-build-flags.patch | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 scripts/bazel/patches/rules-rust-windows-build-flags.patch diff --git a/MODULE.bazel b/MODULE.bazel index 147a82ce34..c6d55d09de 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,9 +6,14 @@ bazel_dep(name = "rules_rust", version = "0.74.0") # Rustc adds every dependency search directory to PATH on Windows; large # graphs exceed its environment limit (rust-lang/rust#110889). Native # shared symlinks preserve artifact identities with one search directory. +# Build-script flags use response files to keep the compiler wrapper's own +# command line within Windows' separate CreateProcessW limit. single_version_override( module_name = "rules_rust", - patches = ["//scripts:bazel/patches/rules-rust-windows-dependency-search.patch"], + patches = [ + "//scripts:bazel/patches/rules-rust-windows-dependency-search.patch", + "//scripts:bazel/patches/rules-rust-windows-build-flags.patch", + ], patch_strip = 1, ) bazel_dep(name = "aspect_rules_js", version = "3.5.1") diff --git a/scripts/bazel/patches/rules-rust-windows-build-flags.patch b/scripts/bazel/patches/rules-rust-windows-build-flags.patch new file mode 100644 index 0000000000..df11b8eb37 --- /dev/null +++ b/scripts/bazel/patches/rules-rust-windows-build-flags.patch @@ -0,0 +1,29 @@ +diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl +--- a/rust/private/rustc.bzl ++++ b/rust/private/rustc.bzl +@@ -1108,7 +1108,8 @@ + for build_env_file in build_env_files: + process_wrapper_flags.add("--env-file", build_env_file) + +- process_wrapper_flags.add_all(build_flags_files, before_each = "--arg-file") ++ if toolchain.exec_triple.system != "windows": ++ process_wrapper_flags.add_all(build_flags_files, before_each = "--arg-file") + + all_allowed_unstable_features = [] + if getattr(ctx.attr, "unstable_rust_features_config", None): +@@ -1591,6 +1592,15 @@ + all_args = [process_wrapper_flags, rustc_path, rustc_flags] + if rust_flags_args != None: + all_args.append(rust_flags_args) ++ if toolchain.exec_triple.system == "windows": ++ # Build-script flags follow authored flags, just as --arg-file does. ++ # The wrapper expands nested response files before starting rustc; ++ # keeping their paths in one response file also bounds its own argv. ++ build_flags = ctx.actions.args() ++ build_flags.set_param_file_format("multiline") ++ build_flags.use_param_file("@%s", use_always = True) ++ build_flags.add_all(build_flags_files, format_each = "@%s") ++ all_args.append(build_flags) + + # Path mapping must be disabled whenever any input string carries a + # `$(location ...)` / `$(execpath ...)` macro: location expansion From 78fa04862d3e180860a296af1fa33e36610d52cb Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 13:05:27 -0700 Subject: [PATCH 03/29] test(hosts): use portable paths and declared runtime inputs Fixes #3189. Fixes #3190. Fixes #3191. Fixes #3192. Fixes #3194. --- crates/tracedecay-cli/src/global.rs | 5 +-- .../test_profile_isolation_test.rs | 2 +- .../code_graph/layered_refresh_tests.rs | 5 +-- crates/tracedecay/BUILD.bazel | 2 +- .../tests/daemon_suite/git_watch_test.rs | 3 +- .../hook_branch_routing_test.rs | 36 +++++++++++++++++++ crates/tracedecay/tests/mcp_suite/fixture.rs | 36 ++++++++++++------- .../mcp_handler_test/active_project_test.rs | 6 ++-- .../tests/transcript_ingest_suite/support.rs | 3 +- .../v2_surface_mount_conformance.rs | 25 +++---------- evals/memory/BUILD.bazel | 5 +++ scripts/bazel/gen_builds.py | 2 ++ 12 files changed, 85 insertions(+), 45 deletions(-) create mode 100644 evals/memory/BUILD.bazel diff --git a/crates/tracedecay-cli/src/global.rs b/crates/tracedecay-cli/src/global.rs index dd02f2a7c1..61f13ebcc4 100644 --- a/crates/tracedecay-cli/src/global.rs +++ b/crates/tracedecay-cli/src/global.rs @@ -371,6 +371,7 @@ mod gather_tests { use std::fs; #[cfg(unix)] use std::os::unix::fs::symlink; + use tracedecay_runtime_core::path_safety::{plain_git_args, plain_host_path}; fn make_enrolled_project(profile: &ProfileRoot, root: &Path, project_id: &str) { tracedecay_runtime_core::storage::pin_fixture_repository_identity(root, project_id) @@ -384,8 +385,8 @@ mod gather_tests { fn git(dir: &Path, args: &[&str]) { let status = std::process::Command::new("git") - .args(args) - .current_dir(dir) + .args(plain_git_args(args)) + .current_dir(plain_host_path(dir)) .status() .unwrap(); assert!(status.success(), "git {args:?}"); diff --git a/crates/tracedecay-cli/tests/core_cli_suite/test_profile_isolation_test.rs b/crates/tracedecay-cli/tests/core_cli_suite/test_profile_isolation_test.rs index 4ad0997712..6c05809f3a 100644 --- a/crates/tracedecay-cli/tests/core_cli_suite/test_profile_isolation_test.rs +++ b/crates/tracedecay-cli/tests/core_cli_suite/test_profile_isolation_test.rs @@ -124,7 +124,7 @@ fn fixture_children_run_only_admitted_fake_hosts_never_ambient_ones() { } fn canonical(path: &Path) -> PathBuf { - path.canonicalize().unwrap_or_else(|_| path.to_path_buf()) + tracedecay_runtime_core::path_safety::canonical_root_identity(path) } #[test] diff --git a/crates/tracedecay-store-runtime/src/session_registry/code_graph/layered_refresh_tests.rs b/crates/tracedecay-store-runtime/src/session_registry/code_graph/layered_refresh_tests.rs index 2ff64920ff..b269feffc7 100644 --- a/crates/tracedecay-store-runtime/src/session_registry/code_graph/layered_refresh_tests.rs +++ b/crates/tracedecay-store-runtime/src/session_registry/code_graph/layered_refresh_tests.rs @@ -22,6 +22,7 @@ use tracedecay_graph_db::{ GraphGenerationRowSpill, GraphGenerationRows, GraphProjectorRevision, NeverCancelled, VerifiedGraphSnapshot, }; +use tracedecay_runtime_core::path_safety::{plain_git_args, plain_host_path}; use super::super::DaemonSessionRuntimeRegistryV1; @@ -29,8 +30,8 @@ const MODULES: usize = 120; fn git(root: &Path, args: &[&str]) { let output = Command::new("git") - .args(args) - .current_dir(root) + .args(plain_git_args(args)) + .current_dir(plain_host_path(root)) .output() .expect("run git fixture command"); assert!( diff --git a/crates/tracedecay/BUILD.bazel b/crates/tracedecay/BUILD.bazel index d16ba05942..183b2f0a97 100644 --- a/crates/tracedecay/BUILD.bazel +++ b/crates/tracedecay/BUILD.bazel @@ -271,7 +271,7 @@ rust_test( rustc_flags = perf_rustc_flags(1), crate_features = ["default", "full", "lite", "production", "rmcp-benchmark", "test-helpers", "test-transport", "token-counting"], compile_data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"] + ["//crates/tracedecay-agent-hosts:src_agents_hermes_templates"], - data = glob(["tests/memory_suite/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:support"] + ["//tests:fixtures"], + data = glob(["tests/memory_suite/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:support"] + ["//tests:fixtures"] + ["//evals/memory:scenarios"], rustc_env = {"CARGO_TARGET_TMPDIR": "/tmp"}, runfiles_env = {"TRACEDECAY_TEST_BIN": "//crates/tracedecay-cli:tracedecay__test"}, ) diff --git a/crates/tracedecay/tests/daemon_suite/git_watch_test.rs b/crates/tracedecay/tests/daemon_suite/git_watch_test.rs index 9eac00a122..a0e03ff37f 100644 --- a/crates/tracedecay/tests/daemon_suite/git_watch_test.rs +++ b/crates/tracedecay/tests/daemon_suite/git_watch_test.rs @@ -120,7 +120,8 @@ fn symbol_count(payload: &Value, name: &str) -> usize { .count() } fn generation_index_len(data_root: &Path, project: &Path) -> usize { - let scope = scoped_code_index_store_root(&data_root.join("code-index-v1"), project); + let project = tracedecay_runtime_core::path_safety::canonical_root_identity(project); + let scope = scoped_code_index_store_root(&data_root.join("code-index-v1"), &project); let pointer: DurablePublicationPointerV1 = serde_json::from_slice( &fs::read(scope.join("active-code-generation-v1.json")).expect("active generation pointer"), ) diff --git a/crates/tracedecay/tests/hooks_lsp_suite/hook_branch_routing_test.rs b/crates/tracedecay/tests/hooks_lsp_suite/hook_branch_routing_test.rs index fff30a04eb..abb7e7fc7c 100644 --- a/crates/tracedecay/tests/hooks_lsp_suite/hook_branch_routing_test.rs +++ b/crates/tracedecay/tests/hooks_lsp_suite/hook_branch_routing_test.rs @@ -32,6 +32,7 @@ fn git(project: &Path, args: &[&str]) { ); } +#[cfg(unix)] fn git_head_oid(project: &Path) -> String { let output = Command::new("git") .args(["rev-parse", "HEAD"]) @@ -49,6 +50,7 @@ fn git_head_oid(project: &Path) -> String { /// anywhere. A subsequent write on another branch rolls the store to a newer /// generation under the new ref while the first branch keeps its sealed /// provenance record. +#[cfg(unix)] #[tokio::test] async fn hook_branch_write_lands_in_a_sealed_single_store_generation() { let dir = TempDir::new().unwrap(); @@ -299,3 +301,37 @@ async fn hook_branch_write_lands_in_a_sealed_single_store_generation() { "no write may create a per-branch database" ); } + +#[cfg(not(unix))] +#[tokio::test] +async fn hook_branch_write_refuses_when_scheduler_authority_is_unavailable() { + let dir = TempDir::new().unwrap(); + let temp_root = canonical_temp_path(dir.path()); + let project = temp_root.join("project"); + std::fs::create_dir_all(&project).unwrap(); + git(&project, &["init", "-b", "main"]); + let harness = ProductionProjectCompositionHarnessV1::open(&temp_root, [project.clone()]) + .await + .unwrap(); + let shard_root = harness.project_data_root(&project).await.unwrap(); + let before = tracedecay_runtime_core::branch_meta::load_branch_meta(&shard_root); + let error = harness + .track_worktree_branch(&project, &project, "feature/hook") + .await + .expect_err("branch activation requires the scheduler authority"); + assert!(matches!( + error, + tracedecay_domain::TraceDecayError::ProjectRoute { + reason_code, + retryable: true, + .. + } if reason_code == "code_index_scheduler_unavailable" + )); + let after = tracedecay_runtime_core::branch_meta::load_branch_meta(&shard_root); + assert_eq!( + serde_json::to_value(before).unwrap(), + serde_json::to_value(after).unwrap() + ); + assert!(!shard_root.join("branches").exists()); + harness.shutdown().await; +} diff --git a/crates/tracedecay/tests/mcp_suite/fixture.rs b/crates/tracedecay/tests/mcp_suite/fixture.rs index 9ae8078581..5e0b732a45 100644 --- a/crates/tracedecay/tests/mcp_suite/fixture.rs +++ b/crates/tracedecay/tests/mcp_suite/fixture.rs @@ -92,7 +92,7 @@ pub fn write_indexed_fixture_sources(project: &Path) { fs::create_dir_all(project.join("src")).unwrap(); fs::write( - project.join("src/main.rs"), + project.join("src").join("main.rs"), r#" use crate::utils::helper; mod utils; @@ -106,7 +106,7 @@ fn main() { .unwrap(); fs::write( - project.join("src/utils.rs"), + project.join("src").join("utils.rs"), r#" /// Returns a greeting string. pub fn helper() -> String { @@ -123,7 +123,7 @@ fn format_greeting(name: &str) -> String { // Test file so affected-tests can find something fs::create_dir_all(project.join("tests")).unwrap(); fs::write( - project.join("tests/test_utils.rs"), + project.join("tests").join("test_utils.rs"), r#" use crate::utils::helper; @@ -188,7 +188,7 @@ impl SeedTargets { /// rewrites the absolute paths embedded in `store_manifest.json`. fn seed_store(flavor: &Path, project_root: &Path, targets: &SeedTargets) -> io::Result<()> { fs::create_dir_all(project_root)?; - let src_home = flavor.join("home/.tracedecay"); + let src_home = flavor.join("home").join(".tracedecay"); let src_data = sole_subdir(&src_home.join("projects"))?; let project_id = default_profile_project_id(project_root); @@ -303,7 +303,7 @@ async fn build_template(dest: &Path) -> io::Result<()> { let project = root.join("project"); fs::create_dir_all(&project)?; - let profile_root = root.join("home/.tracedecay"); + let profile_root = root.join("home").join(".tracedecay"); let global_db_path = profile_root.join("global.db"); let options = TraceDecayOpenOptions { profile_root: Some(profile_root.clone()), @@ -558,7 +558,9 @@ async fn changing_a_schema_column_constant_does_not_reuse_the_template() { let global_db = selected .join(EMPTY_FLAVOR) - .join("home/.tracedecay/global.db"); + .join("home") + .join(".tracedecay") + .join("global.db"); let connection = Connection::open(&global_db).unwrap(); let mut statement = connection .prepare("SELECT name FROM pragma_table_info('graph_scopes') ORDER BY cid") @@ -594,9 +596,15 @@ async fn template_recorded_at_an_older_git_correlation_version_is_rebuilt() { // What a build before the Git correlation bump left in a warm target. let stale = ensure_template(&tmp_root, older).await.unwrap(); - let stale_sessions = sole_subdir(&stale.join(EMPTY_FLAVOR).join("home/.tracedecay/projects")) - .unwrap() - .join(tracedecay_runtime_core::storage::SESSIONS_DB_FILENAME); + let stale_sessions = sole_subdir( + &stale + .join(EMPTY_FLAVOR) + .join("home") + .join(".tracedecay") + .join("projects"), + ) + .unwrap() + .join(tracedecay_runtime_core::storage::SESSIONS_DB_FILENAME); let downgraded = Connection::open(&stale_sessions) .unwrap() .execute( @@ -610,7 +618,7 @@ async fn template_recorded_at_an_older_git_correlation_version_is_rebuilt() { ); let template = ensure_template(&tmp_root, current).await.unwrap(); - let profile_root = tmp_root.join("home/.tracedecay"); + let profile_root = tmp_root.join("home").join(".tracedecay"); let cg = match init_project_from_template_root( Some(&template), &tmp_root.join("project"), @@ -649,7 +657,9 @@ async fn template_seed_opens_the_copied_store_for_the_requested_project() { let template_store = sole_subdir( &template .join(EMPTY_FLAVOR) - .join("home/.tracedecay/projects"), + .join("home") + .join(".tracedecay") + .join("projects"), ) .unwrap(); let template_manifest: Value = serde_json::from_str( @@ -668,7 +678,7 @@ async fn template_seed_opens_the_copied_store_for_the_requested_project() { let project = tmp_root.join("requested-project"); fs::create_dir_all(&project).unwrap(); - let profile_root = tmp_root.join("profile/.tracedecay"); + let profile_root = tmp_root.join("profile").join(".tracedecay"); let cg = init_project_from_template_root( Some(&template), &project, @@ -713,7 +723,7 @@ async fn template_seed_does_not_fall_back_when_the_destination_exists() { .expect("template builds from a real init"); let project = tmp_root.join("requested-project"); fs::create_dir_all(&project).unwrap(); - let profile_root = tmp_root.join("profile/.tracedecay"); + let profile_root = tmp_root.join("profile").join(".tracedecay"); let project_id = default_profile_project_id(&project); fs::create_dir_all(profile_root.join("projects").join(&project_id)).unwrap(); diff --git a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/active_project_test.rs b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/active_project_test.rs index e0528ea3e4..d321729e50 100644 --- a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/active_project_test.rs +++ b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/active_project_test.rs @@ -231,10 +231,8 @@ async fn open_checkout() -> OpenedCheckout { ); let project_root = canonical_existing_identity(&project_root).expect("canonical project"); - let isolation_root = isolation - .path() - .canonicalize() - .expect("canonical isolation"); + let isolation_root = + canonical_existing_identity(isolation.path()).expect("canonical isolation"); let harness = ProductionProjectCompositionHarnessV1::open_with_scope_prefix( &isolation_root, [project_root.clone()], diff --git a/crates/tracedecay/tests/transcript_ingest_suite/support.rs b/crates/tracedecay/tests/transcript_ingest_suite/support.rs index 5cd111a6f1..44e76ad13a 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/support.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/support.rs @@ -48,8 +48,9 @@ pub fn assert_metadata_path_eq(actual: &serde_json::Value, expected: &Path) { pub fn assert_sanitized_path_text_eq(actual: &str, expected: &Path) { const REDACTED: &str = "[TraceDecay redacted: high-entropy token]"; let expected_text = normalize_path_text(&expected.to_string_lossy()); + let actual_text = normalize_path_text(actual); let mut cursor = 0; - for part in actual.split(REDACTED).filter(|part| !part.is_empty()) { + for part in actual_text.split(REDACTED).filter(|part| !part.is_empty()) { let found = expected_text[cursor..] .find(part) .map(|index| cursor + index); diff --git a/crates/tracedecay/tests/transport_acceptance_suite/v2_surface_mount_conformance.rs b/crates/tracedecay/tests/transport_acceptance_suite/v2_surface_mount_conformance.rs index 33c096826b..8d8d21dbb5 100644 --- a/crates/tracedecay/tests/transport_acceptance_suite/v2_surface_mount_conformance.rs +++ b/crates/tracedecay/tests/transport_acceptance_suite/v2_surface_mount_conformance.rs @@ -57,7 +57,7 @@ use tracedecay_api::{ is_http_application_operation_exposed, }; use tracedecay_contracts::catalog_composition::build_application_catalog_snapshot; -use tracedecay_runtime_core::ast_grep::AST_GREP_BIN_ENV; +use tracedecay_runtime_core::ast_grep::{AST_GREP_BIN_ENV, ast_grep_command}; use tracedecay_session_memory::event_lane::ActivityFamilyV1; use tracedecay_tool_catalog::{ ApplicationSurfaceOperation, BindingSurface, CapabilityManifestV1, CatalogSnapshotV1, @@ -193,7 +193,7 @@ impl MountFixture { common::initialize_tracedecay_cli_project(&home, &project); let daemon = common::spawn_tracedecay_daemon_with(&home, |command| { - command.env(AST_GREP_BIN_ENV, ast_grep_executable()); + command.env(AST_GREP_BIN_ENV, ast_grep_command().get_program()); }); let authority = wait_for_http_authority(&common::daemon_authority_path(&profile)); @@ -244,29 +244,14 @@ impl MountFixture { fn isolated_command(home: &Path) -> Command { let mut command = common::tracedecay_command_with_home(home); + // Isolated children retain only system PATH entries. Pass the executable + // selected by the shared resolver, including npm's native Windows binary. command .env("TRACEDECAY_TEST_ALLOW_INCOMPLETE_HOLDER_SCAN", "1") - .env(AST_GREP_BIN_ENV, ast_grep_executable()); + .env(AST_GREP_BIN_ENV, ast_grep_command().get_program()); command } -/// The ast-grep executable the source-edit bindings are graded against. -/// -/// A fixture child's PATH is the system directories only, and an npm-installed -/// ast-grep never lives there, so the child is handed the test process's own -/// executable through the product's explicit override instead. -fn ast_grep_executable() -> PathBuf { - let name = format!("ast-grep{}", std::env::consts::EXE_SUFFIX); - std::env::var_os(AST_GREP_BIN_ENV) - .map(PathBuf::from) - .or_else(|| { - std::env::split_paths(&std::env::var_os("PATH")?) - .map(|dir| dir.join(&name)) - .find(|path| path.is_file()) - }) - .expect("ast-grep must be on the test PATH to grade the ast-grep bindings") -} - /// The dashboard server's closed operation->route table, restated in the test /// so the probe does not derive its routes from the catalog it grades. /// diff --git a/evals/memory/BUILD.bazel b/evals/memory/BUILD.bazel new file mode 100644 index 0000000000..af098d6a49 --- /dev/null +++ b/evals/memory/BUILD.bazel @@ -0,0 +1,5 @@ +filegroup( + name = "scenarios", + srcs = glob(["scenarios/*.json"]), + visibility = ["//crates/tracedecay:__pkg__"], +) diff --git a/scripts/bazel/gen_builds.py b/scripts/bazel/gen_builds.py index a9dd631da3..3dc05179c8 100644 --- a/scripts/bazel/gen_builds.py +++ b/scripts/bazel/gen_builds.py @@ -1081,6 +1081,8 @@ def unique_name(want): # Suites read the shared fixtures as ../../tests/fixtures from the # crate directory, where the launcher starts them. data += ' + ["//tests:fixtures"]' + if name == "tracedecay" and t["name"] == "memory_suite": + data += ' + ["//evals/memory:scenarios"]' # include_str!/include_bytes! are compile-time inputs. They cover crate-root # resource dirs beside tests/ and repo-root fixtures under //tests. compile_data = ( From a33b89d6d20fb4e58af0d57cdb582cf20389b5e0 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 13:05:28 -0700 Subject: [PATCH 04/29] test(runtime): settle owners before offline fixture operations Fixes #3193. Fixes #3195. Fixes #3196. Fixes #3198. --- .../code_index_scheduler/tests/residency.rs | 4 ++- .../tests/text_artifact_carry_tests.rs | 8 ++++++ .../src/test_support/host_admission.rs | 28 +++++++++++++++++++ .../src/operations/generation.rs | 16 ++++++++--- .../projector_upgrade.rs | 19 +++++++++++-- .../restart_atomicity.rs | 4 +++ 6 files changed, 72 insertions(+), 7 deletions(-) diff --git a/crates/tracedecay-code-index-runtime/src/code_index_scheduler/tests/residency.rs b/crates/tracedecay-code-index-runtime/src/code_index_scheduler/tests/residency.rs index 462343ba2e..1f789df72e 100644 --- a/crates/tracedecay-code-index-runtime/src/code_index_scheduler/tests/residency.rs +++ b/crates/tracedecay-code-index-runtime/src/code_index_scheduler/tests/residency.rs @@ -59,8 +59,10 @@ async fn an_idle_worktree_gives_back_its_decode_and_search_still_answers_fresh() .expect("the seated generation answers"); assert!(!fresh.served_stale); let generation = fresh.generation.as_str().to_owned(); - // A pass still finishing the mount can re-seat the decode it measures. + // Attribution can hold the decode while awaiting admission after the + // source pass settles. Wait for the worker to release that last handle. wait_for_settled_owner(®istry, fixture.path()).await; + wait_for_worker_phase(®istry, fixture.path(), CodeIndexWorkerPhaseV1::Parked).await; let used = owners.report(Instant::now()); assert_eq!( diff --git a/crates/tracedecay-code-index-runtime/src/code_index_scheduler/tests/text_artifact_carry_tests.rs b/crates/tracedecay-code-index-runtime/src/code_index_scheduler/tests/text_artifact_carry_tests.rs index 9f75d31ec5..dfc036ad79 100644 --- a/crates/tracedecay-code-index-runtime/src/code_index_scheduler/tests/text_artifact_carry_tests.rs +++ b/crates/tracedecay-code-index-runtime/src/code_index_scheduler/tests/text_artifact_carry_tests.rs @@ -315,6 +315,9 @@ fn assert_edit_over_damaged_parent_builds_cold(damage: impl FnOnce(&Path)) { ); published(scheduler.reconcile_now().expect("publish the parent")); drain_text(&scheduler); + // Corruption is an offline store change: Windows refuses to mutate an + // artifact while the scheduler still owns its mapped serving pages. + drop(scheduler); damage(&active_text_artifact_path(store.path())); fixture.edit( @@ -322,6 +325,11 @@ fn assert_edit_over_damaged_parent_builds_cold(damage: impl FnOnce(&Path)) { &module(4, "").replace("wrapping_mul(31)", "wrapping_mul(37)"), ); fixture.commit_all("edit over a damaged parent"); + let mut scheduler = super::scheduler( + &fixture, + store.path().to_path_buf(), + Arc::new(SharedCodeIndexBytePoolV1::default()), + ); published(scheduler.reconcile_now().expect("publish the successor")); drain_text(&scheduler); let built = active_artifact(store.path()); diff --git a/crates/tracedecay-project/src/test_support/host_admission.rs b/crates/tracedecay-project/src/test_support/host_admission.rs index 426a005ff2..93629f1173 100644 --- a/crates/tracedecay-project/src/test_support/host_admission.rs +++ b/crates/tracedecay-project/src/test_support/host_admission.rs @@ -402,6 +402,34 @@ impl HostAdmissionTestRuntimeV1 { Arc::clone(&self.session_registry) } + /// Joins this profile's owners and closes their stores before an offline + /// fixture mutation. Other runtimes sharing the profile must be dropped first. + pub async fn shutdown(self) -> Result<()> { + let Self { + session_registry, + profile_database, + profile_registered, + project_registered, + _database_scope: database_scope, + .. + } = self; + drop((profile_database, profile_registered, project_registered)); + session_registry.cancel_store_opens_for_shutdown(); + session_registry.cancel_terminal_tasks(); + session_registry.cancel_memory_graph_reconciliation_tasks(); + let terminal = session_registry.shutdown_terminal_tasks().await; + let reconciliation = session_registry + .shutdown_memory_graph_reconciliation_tasks() + .await; + terminal.map_err(|message| TraceDecayError::Config { message })?; + reconciliation.map_err(|message| TraceDecayError::Config { message })?; + session_registry + .close_retained_graph_runtimes_for_shutdown() + .await?; + drop((session_registry, database_scope)); + Ok(()) + } + #[doc(hidden)] pub async fn checkpoint_session_database_for_test( &self, diff --git a/crates/tracedecay-session-temporal-store/src/operations/generation.rs b/crates/tracedecay-session-temporal-store/src/operations/generation.rs index a91e6f68f7..7ab1cadfc9 100644 --- a/crates/tracedecay-session-temporal-store/src/operations/generation.rs +++ b/crates/tracedecay-session-temporal-store/src/operations/generation.rs @@ -735,10 +735,18 @@ mod tests { let mut affected = 0_usize; let mut final_rewind = None; for _ in 0..32 { - let page = - invalidate_raw_summary_revision(&conn, "cursor", "large-closure", 100, PAGE_LIMIT) - .await - .unwrap(); + // Production commits one bounded invalidation page atomically. + let transaction = conn.transaction().await.unwrap(); + let page = invalidate_raw_summary_revision( + &transaction, + "cursor", + "large-closure", + 100, + PAGE_LIMIT, + ) + .await + .unwrap(); + transaction.commit().await.unwrap(); assert!(page.stale_summary_count <= PAGE_LIMIT); affected = affected.saturating_add(page.stale_summary_count); final_rewind = Some(page.rewind_frontier_store_id); diff --git a/crates/tracedecay/tests/transcript_ingest_suite/projector_upgrade.rs b/crates/tracedecay/tests/transcript_ingest_suite/projector_upgrade.rs index 5d1360e104..1f1fa4a4f1 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/projector_upgrade.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/projector_upgrade.rs @@ -102,7 +102,11 @@ fn reset_store(db_path: &Path) { for suffix in ["-wal", "-shm"] { let mut sidecar = db_path.as_os_str().to_owned(); sidecar.push(suffix); - std::fs::remove_file(sidecar).ok(); + match std::fs::remove_file(sidecar) { + Ok(()) => {} + Err(error) if error.kind() == std::io::ErrorKind::NotFound => {} + Err(error) => panic!("remove closed store {suffix}: {error}"), + } } } @@ -153,9 +157,16 @@ async fn shipped_store_is_reset_required_and_a_reset_store_serves_both_messages( search_texts(&db, "claude", "claudeprobe").await, [CLAUDE_TEXT] ); - drop(db); + db.shutdown() + .await + .expect("close stores before shaping the shipped schema"); shape_store_as_shipped(&db_path); + // Retain the profile owner across the refused project mount so its + // partially opened workers can be joined before the offline reset. + let profile = HostAdmissionTestRuntimeV1::profile(&profile_root) + .await + .expect("mount the current profile around the shipped project store"); let refused = HostAdmissionTestRuntimeV1::project(&profile_root, &project, project_id.clone()) .await .err() @@ -184,6 +195,10 @@ async fn shipped_store_is_reset_required_and_a_reset_store_serves_both_messages( "a refused store is left for the operator's reset, never rewritten" ); + profile + .shutdown() + .await + .expect("close refused project mount before reset"); reset_store(&db_path); let reset = open_project_session_db(&project).await.unwrap(); ingest_both(&reset, &home, &project, &cursor_transcript).await; diff --git a/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs b/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs index d5677097c6..09958db2b1 100644 --- a/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs +++ b/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs @@ -70,6 +70,10 @@ pub(super) struct ProjectSessionTestRuntime { } impl ProjectSessionTestRuntime { + pub(super) async fn shutdown(self) -> tracedecay_domain::errors::Result<()> { + self.runtime.shutdown().await + } + pub(super) fn runtime(&self) -> &HostAdmissionTestRuntimeV1 { &self.runtime } From 53262769273fb5add3045089b0d312beadb6345b Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 13:05:29 -0700 Subject: [PATCH 05/29] test(dashboard): synchronize asynchronous curator receipt checks Fixes #3187. --- .../tests/dashboard_api_test/automation.rs | 19 ++------ .../dashboard_api_support.rs | 48 +++++++++++++++---- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/crates/tracedecay/tests/dashboard_api_test/automation.rs b/crates/tracedecay/tests/dashboard_api_test/automation.rs index 811f259c26..e62e65ab7c 100644 --- a/crates/tracedecay/tests/dashboard_api_test/automation.rs +++ b/crates/tracedecay/tests/dashboard_api_test/automation.rs @@ -417,7 +417,6 @@ fn final_self_improvement_smoke_covers_autonomous_curation_and_skill_deployment( /// admission with the run's receipt, and the run settles on the daemon. #[test] fn fact_store_curate_answers_with_its_receipt_while_a_slow_curator_runs() { - const CURATOR_TURN: Duration = Duration::from_secs(3); let runtime = create_runtime(); runtime.block_on(async { let tmp = tempdir_or_panic(); @@ -430,10 +429,9 @@ fn fact_store_curate_answers_with_its_receipt_while_a_slow_curator_runs() { let (cg, host_runtime) = setup_project(&profile, &project_root).await; let fixture = seed_memory_fixture(&cg).await; - let slow_codex = FakeCodexAppServer::new_memory_curator_answering_after( + let slow_codex = FakeCodexAppServer::new_memory_curator_held( fixture.near_duplicate_fact_id.clone(), fixture.near_duplicate_last_event_id.clone(), - CURATOR_TURN, ); let project_id = cg .configuration_runtime() @@ -471,7 +469,6 @@ fn fact_store_curate_answers_with_its_receipt_while_a_slow_curator_runs() { assert_eq!(status, 200, "automation config patch failed: {config}"); let run_id = "request.dashboard.slow-curator-receipt"; - let started = Instant::now(); let response = agent .post(format!( "{base_url}/api/application/retained/fact_store_curate" @@ -482,7 +479,6 @@ fn fact_store_curate_answers_with_its_receipt_while_a_slow_curator_runs() { "min_confidence_millionths": 500_000 })) .unwrap_or_else(|error| panic!("POST fact_store_curate failed: {error}")); - let answered = started.elapsed(); let (status, receipt) = response_to_json(response); assert_eq!(status, 200, "fact_store_curate failed: {receipt}"); let payload = &receipt["value"]["outcome"]["value"]["payload"]; @@ -499,16 +495,11 @@ fn fact_store_curate_answers_with_its_receipt_while_a_slow_curator_runs() { receipt["value"]["outcome"]["value"]["reconciliation"], "pending" ); - assert!( - answered < Duration::from_millis(250), - "fact_store_curate answered after {answered:?}, behind a {CURATOR_TURN:?} curator" - ); - + // The backend cannot complete before release, so receiving this receipt + // proves admission does not await the curator, independent of CPU load. + slow_codex.wait_for_turn(); + slow_codex.release_turn(); let settled = wait_for_settled_run(&agent, &base_url, run_id); - assert!( - started.elapsed() >= CURATOR_TURN, - "the run settled before its curator answered" - ); assert_eq!(settled["status"], "succeeded", "{settled}"); assert_eq!(settled["trigger"], "application"); assert_eq!(settled["accepted_count"], 1); diff --git a/crates/tracedecay/tests/dashboard_api_test/dashboard_api_support.rs b/crates/tracedecay/tests/dashboard_api_test/dashboard_api_support.rs index 7da09a0c40..005a6fd66c 100644 --- a/crates/tracedecay/tests/dashboard_api_test/dashboard_api_support.rs +++ b/crates/tracedecay/tests/dashboard_api_test/dashboard_api_support.rs @@ -912,15 +912,13 @@ pub(crate) struct FakeCodexAppServer { impl FakeCodexAppServer { pub(crate) fn new_memory_curator(fact_id: FactId, last_event_id: FactEventId) -> Self { - Self::new_memory_curator_answering_after(fact_id, last_event_id, Duration::ZERO) + let server = Self::new_memory_curator_held(fact_id, last_event_id); + server.release_turn(); + server } - /// A curator backend that holds each turn for `delay` before answering. - pub(crate) fn new_memory_curator_answering_after( - fact_id: FactId, - last_event_id: FactEventId, - delay: Duration, - ) -> Self { + /// A curator backend whose answer waits for the test's explicit release. + pub(crate) fn new_memory_curator_held(fact_id: FactId, last_event_id: FactEventId) -> Self { let temp = tempdir_or_panic(); let script_path = temp.path().join("codex.py"); let bin = fake_codex_bin(temp.path()); @@ -948,7 +946,12 @@ for line in sys.stdin: "result": {"thread": {"id": "thread-dashboard", "model": "dashboard-fake-model"}} }), flush=True) elif method == "turn/start": - time.sleep(__TURN_DELAY_SECONDS__) + with open(__TURN_STARTED__, "w") as started: + started.write("started") + while not os.path.exists(__TURN_RELEASE__): + if not os.path.isdir(os.path.dirname(__TURN_RELEASE__)): + sys.exit(44) + time.sleep(0.01) payload = { "ops": [{ "op": "normalize_tags", @@ -981,11 +984,38 @@ for line in sys.stdin: &serde_json::to_string(last_event_id.as_str()) .unwrap_or_else(|error| panic!("encode fake curator event id: {error}")), ) - .replace("__TURN_DELAY_SECONDS__", &delay.as_secs_f64().to_string()); + .replace( + "__TURN_STARTED__", + &serde_json::to_string(&temp.path().join("turn-started")).unwrap(), + ) + .replace( + "__TURN_RELEASE__", + &serde_json::to_string(&temp.path().join("turn-release")).unwrap(), + ); write_file(&script_path, &script); install_fake_codex_launcher(&script_path, &bin); Self { _temp: temp, bin } } + + pub(crate) fn wait_for_turn(&self) { + crate::common::poll_until( + Instant::now() + Duration::from_secs(60), + Duration::from_millis(25), + || { + self._temp + .path() + .join("turn-started") + .try_exists() + .expect("read curator turn marker") + .then_some(()) + }, + || "curator did not reach its held turn".to_owned(), + ); + } + + pub(crate) fn release_turn(&self) { + write_file(&self._temp.path().join("turn-release"), "release"); + } } pub(crate) async fn start_dashboard_fixture(seed_lcm: bool) -> DashboardFixture { From 7abf5daf241858b222541460141bc088f1089a54 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 13:05:30 -0700 Subject: [PATCH 06/29] fix(storage): keep codec compression consistent across platforms Use the workspace flate2 backend for Windows ZIP extraction without activating another backend for content-addressed sealed storage. Fixes #3197. --- Cargo.lock | 13 ------------- crates/tracedecay-cli/Cargo.toml | 4 +++- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75ee39a4e5..7d0171c222 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8225,7 +8225,6 @@ dependencies = [ "indexmap", "memchr", "typed-path", - "zopfli", ] [[package]] @@ -8240,18 +8239,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] - [[package]] name = "zvariant" version = "5.15.0" diff --git a/crates/tracedecay-cli/Cargo.toml b/crates/tracedecay-cli/Cargo.toml index 896c8e2343..16b82cab84 100644 --- a/crates/tracedecay-cli/Cargo.toml +++ b/crates/tracedecay-cli/Cargo.toml @@ -203,7 +203,9 @@ mimalloc = { version = "0.1.52", optional = true, features = ["no_thp"] } # Windows release archives are `.zip` (Unix ships `.tar.gz` via flate2/tar); # consumed by the self-update extraction in src/upgrade.rs. [target.'cfg(windows)'.dependencies] -zip = { version = "8", default-features = false, features = ["deflate"] } +# Keep the flate2 backend selected by the workspace: sealed segment digests +# must not change when Windows archive support is enabled. +zip = { version = "8", default-features = false, features = ["deflate-flate2"] } # sigstore-verify links aws-lc; on x86_64 MSVC it needs NASM unless its # prebuilt assembly objects are allowed, and release runners carry no NASM. aws-lc-rs = { version = "1.18", features = ["prebuilt-nasm"] } From ce0c60483eb20d484d292401eda8577eedd63343 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 13:30:00 -0700 Subject: [PATCH 07/29] fix(sessions): require evidence for the exact refresh frontier --- .../src/projection/tests.rs | 96 +++++++++++++++++-- .../src/rebuild.rs | 27 ++---- .../src/test_registered_impls.rs | 31 ++++++ 3 files changed, 129 insertions(+), 25 deletions(-) diff --git a/crates/tracedecay-session-temporal-store/src/projection/tests.rs b/crates/tracedecay-session-temporal-store/src/projection/tests.rs index 71185aa708..5ad4ac4286 100644 --- a/crates/tracedecay-session-temporal-store/src/projection/tests.rs +++ b/crates/tracedecay-session-temporal-store/src/projection/tests.rs @@ -487,10 +487,8 @@ async fn all_skipped_refresh_persists_reopens_and_completes_idempotently() { ProviderId::new(format!("projector-test-{ordinal}")).unwrap(), ObservationId::new(format!("record.projector.{ordinal}")).unwrap(), CanonicalObservationRelationsV1::new(session_id.clone()), - vec![CanonicalObservationFactV1::Git { - evidence_kind: tracedecay_domain::CanonicalGitEvidenceKindV1::Branch, - reference: Some("refs/heads/main".to_owned()), - content: None, + vec![CanonicalObservationFactV1::Boundary { + boundary_kind: tracedecay_domain::CanonicalBoundaryKindV1::TurnStart, }], None, ); @@ -579,8 +577,55 @@ async fn all_skipped_refresh_persists_reopens_and_completes_idempotently() { } } +#[tokio::test] +async fn refresh_at_committed_frontier_needs_no_new_observation_effect() { + let tmp = TempDir::new().unwrap(); + let runtime = HostAdmissionTestRuntimeV1::profile(tmp.path()) + .await + .unwrap(); + let session_id = fixture_session("session.projector.unchanged-frontier"); + let (observation, write) = fixture_observation(&session_id, 0, None, false); + Box::pin(persist_fixture(&runtime, observation, write)).await; + let store = temporal_store(&runtime); + let baseline = refresh_through(&store, &session_id, 1, 0).await; + let refreshed = refresh_through(&store, &session_id, 1, 1).await; + assert_ne!(refreshed, baseline); + assert_eq!( + runtime + .session_temporal_fixture_count_for_test( + HostAdmissionScope::Profile, + SessionTemporalFixtureCountV1::Occurrences + ) + .await + .unwrap(), + 1 + ); +} + #[tokio::test] async fn refresh_frontier_without_observation_effects_cannot_activate() { + assert_unsupported_frontier_cannot_activate(FrontierPrefix::Absent, false).await; +} + +#[tokio::test] +async fn skipped_prefix_cannot_prove_an_unsupported_target_frontier() { + for foreign_target in [false, true] { + assert_unsupported_frontier_cannot_activate(FrontierPrefix::Skipped, foreign_target).await; + } +} + +#[tokio::test] +async fn message_prefix_cannot_prove_an_unsupported_target_frontier() { + assert_unsupported_frontier_cannot_activate(FrontierPrefix::Message, false).await; +} + +enum FrontierPrefix { + Absent, + Skipped, + Message, +} + +async fn assert_unsupported_frontier_cannot_activate(prefix: FrontierPrefix, foreign_target: bool) { let tmp = TempDir::new().unwrap(); let runtime = HostAdmissionTestRuntimeV1::profile(tmp.path()) .await @@ -590,10 +635,42 @@ async fn refresh_frontier_without_observation_effects_cannot_activate() { Box::pin(persist_fixture(&runtime, observation, write)).await; let store = temporal_store(&runtime); refresh_through(&store, &session_id, 1, 0).await; + let target = if matches!(prefix, FrontierPrefix::Skipped) { + let (observation, write) = fixture_observation_from_facts( + &session_id, + 1, + ProviderId::new("projector-test-1").unwrap(), + ObservationId::new("record.projector.1").unwrap(), + CanonicalObservationRelationsV1::new(session_id.clone()), + vec![CanonicalObservationFactV1::Boundary { + boundary_kind: tracedecay_domain::CanonicalBoundaryKindV1::TurnStart, + }], + None, + ); + assert_eq!( + tracedecay_store::derive_canonical_projection(&observation) + .unwrap() + .skip_reason(), + Some(ProjectionSkipReason::NonConversationalRecord) + ); + Box::pin(persist_fixture(&runtime, observation, write)).await; + 3 + } else if matches!(prefix, FrontierPrefix::Message) { + let (observation, write) = fixture_observation(&session_id, 1, None, false); + Box::pin(persist_fixture(&runtime, observation, write)).await; + 3 + } else { + 2 + }; + if foreign_target { + let foreign_session = fixture_session("session.projector.foreign-frontier"); + let (observation, write) = fixture_observation(&foreign_session, 2, None, false); + Box::pin(persist_fixture(&runtime, observation, write)).await; + } store .begin_or_join_session_refresh(SessionRefreshBeginOrJoinRequestV1::new( session_id.clone(), - SessionRefreshFrontierV1::new(2, 1).unwrap(), + SessionRefreshFrontierV1::new(target, 1).unwrap(), )) .await .unwrap(); @@ -607,8 +684,11 @@ async fn refresh_frontier_without_observation_effects_cannot_activate() { .await .unwrap() .unwrap(); - assert!(batch.occurrences().is_empty()); - assert_eq!(progress.frontier().committed_through(), 2); + assert_eq!( + batch.occurrences().len(), + usize::from(matches!(prefix, FrontierPrefix::Message)) + ); + assert_eq!(progress.frontier().committed_through(), target); let request = SessionRefreshCompletionRequestV1::new( progress.operation_id().clone(), session_id.clone(), @@ -623,7 +703,7 @@ async fn refresh_frontier_without_observation_effects_cannot_activate() { let error = store .complete_session_refresh(request, ExecutionControl::default()) .await - .expect_err("an empty batch cannot prove an unsupported source frontier"); + .expect_err("a prefix batch cannot prove an unsupported source frontier"); assert!(matches!(error, SessionStoreError::Storage { .. })); assert_eq!( store diff --git a/crates/tracedecay-session-temporal-store/src/rebuild.rs b/crates/tracedecay-session-temporal-store/src/rebuild.rs index aac5d91612..614a6a49d7 100644 --- a/crates/tracedecay-session-temporal-store/src/rebuild.rs +++ b/crates/tracedecay-session-temporal-store/src/rebuild.rs @@ -232,32 +232,25 @@ pub(super) async fn validate_candidate_frontier( } expected.insert(occurrence_id.clone()); } - if expected.is_empty() && source_frontier != base_frontier { - // A frontier advance whose every observation was deterministically - // skipped (effects recorded with zero outputs, e.g. host - // non-conversational records) legitimately introduces no canonical - // outputs. Refusing those activations wedged the refresh worker in a - // permanent retry loop. Only an advance that processed no - // observation at all is a validation failure. - let mut processed_any = conn + if source_frontier != base_frontier { + // Discovery targets an effect belonging to this session. Skipped + // effects prove an empty advance, but an earlier effect or another + // session's effect cannot prove the requested endpoint. + let mut target_effect = conn .query( "SELECT EXISTS ( SELECT 1 FROM session_temporal_observation_effects - WHERE session_id = ?1 - AND observation_sequence > ?2 - AND observation_sequence <= ?3 + WHERE session_id = ?1 AND observation_sequence = ?2 )", params![ session_id, - i64::try_from(base_frontier) - .map_err(|error| storage(ACTIVATE_OPERATION, error))?, i64::try_from(source_frontier) .map_err(|error| storage(ACTIVATE_OPERATION, error))? ], ) .await .map_err(|error| storage(ACTIVATE_OPERATION, error))?; - let processed = match processed_any + let supported = match target_effect .next() .await .map_err(|error| storage(ACTIVATE_OPERATION, error))? @@ -269,11 +262,11 @@ pub(super) async fn validate_candidate_frontier( } None => false, }; - drop(processed_any); - if !processed { + drop(target_effect); + if !supported { return Err(storage_message( ACTIVATE_OPERATION, - "candidate generation advanced its source frontier without processing any observations", + "candidate source frontier has no observation effect for this session", )); } } diff --git a/crates/tracedecay-session-temporal-store/src/test_registered_impls.rs b/crates/tracedecay-session-temporal-store/src/test_registered_impls.rs index caf537f245..c83afa73e1 100644 --- a/crates/tracedecay-session-temporal-store/src/test_registered_impls.rs +++ b/crates/tracedecay-session-temporal-store/src/test_registered_impls.rs @@ -16,6 +16,7 @@ use tracedecay_global_db::{ use tracedecay_runtime_core::db::DatabaseEngineReadSnapshot; use tracedecay_runtime_core::db::engine::{ Connection, Error as EngineError, Executor, IntoParams, QueryExecutor, Rows, TestConnection, + Transaction, }; use tracedecay_store::StoreShardScopeV1; @@ -84,6 +85,36 @@ impl SessionTemporalExec for TestConnection { } } +impl SessionTemporalQuery for Transaction { + fn query

( + &self, + sql: &str, + params: P, + ) -> impl Future> + Send + where + P: IntoParams + Send, + { + Transaction::query(self, sql, params) + } +} + +impl SessionTemporalExec for Transaction { + fn execute

( + &self, + sql: &str, + params: P, + ) -> impl Future> + Send + where + P: IntoParams + Send, + { + Transaction::execute(self, sql, params) + } + + fn execute_batch(&self, sql: &str) -> impl Future> + Send { + Transaction::execute_batch(self, sql) + } +} + impl SessionTemporalQuery for RegisteredGlobalDbWriteTransaction<'_> { fn query

( &self, From 6288324b8a4012b0541544f13377b25af1772143 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 13:49:37 -0700 Subject: [PATCH 08/29] fix(bazel): include memory evaluation fixture package --- .bazelignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.bazelignore b/.bazelignore index 1b5d52ff9e..15b2d8aab0 100644 --- a/.bazelignore +++ b/.bazelignore @@ -9,6 +9,5 @@ target .codex-worktrees .claude-worktrees .claude/worktrees -evals mockups vendor From 652062edf1d920b526d2070fd541a9245159e3ad Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 14:12:59 -0700 Subject: [PATCH 09/29] test(sessions): batch large history fixture writes --- .../src/session_retrieval/tests.rs | 309 ++++++++++++------ .../tests/dashboard_api_test/runtime.rs | 55 ++-- 2 files changed, 236 insertions(+), 128 deletions(-) diff --git a/crates/tracedecay-session-runtime/src/session_retrieval/tests.rs b/crates/tracedecay-session-runtime/src/session_retrieval/tests.rs index 598ab7c1e9..1898dc6540 100644 --- a/crates/tracedecay-session-runtime/src/session_retrieval/tests.rs +++ b/crates/tracedecay-session-runtime/src/session_retrieval/tests.rs @@ -170,8 +170,34 @@ async fn seed_real_page_fixture_in_session( session_id: String, finalize: bool, ) -> RealPageFixture { - let message_id = format!("message.page.{rank:02}"); - let text = format!("canonical content {rank}"); + seed_real_page_fixtures_in_session( + database, + root, + rank..rank + 1, + provider, + session_id, + finalize, + ) + .await + .pop() + .expect("one seeded page fixture") +} + +async fn seed_real_page_fixtures_in_session( + database: &tracedecay_global_db::RegisteredGlobalDb, + root: &TemporalAuthorizedRoot, + ranks: std::ops::Range, + provider: String, + session_id: String, + finalize: bool, +) -> Vec { + // Match the host admission projector's bounded transaction window. + const BATCH_ITEMS: usize = 32; + let rank = ranks + .clone() + .next_back() + .expect("at least one fixture rank"); + let scalar = ranks.len() == 1; assert!( database .upsert_session(&SessionRecord { @@ -197,97 +223,144 @@ async fn seed_real_page_fixture_in_session( let session = SessionId::new(session_id.clone()).expect("session"); let source = ObservationSourceIdentityV1::for_provider(provider_id.clone(), session.clone()) .expect("observation source"); - let ordinal = u64::try_from(rank).expect("ordinal"); - let range = ObservationSourceRangeV1::new(ordinal, ordinal + 1).expect("source range"); - let record_id = ObservationId::new(format!("record.page.{rank:02}")).expect("record id"); - let projected_message_id = if provider == "claude" { - record_id.as_str().to_owned() - } else { - message_id.clone() - }; - let relations = CanonicalObservationRelationsV1::new(session) - .with_message_id(ObservationId::new(message_id.clone()).expect("message id")); - let envelope = CanonicalObservationEnvelopeV1::new( - provider_id, - "message", - record_id.clone(), - relations, - vec![CanonicalObservationFactV1::Message { - role: CanonicalMessageRoleV1::Assistant, - content: json!(text), - model: Some("fixture-model".to_owned()), - timestamp: Some(i64::try_from(rank).expect("timestamp")), - }], - CanonicalObservationEvidenceV1::new(ObservationOrderingDomainV1::SnapshotOrder, range), - ) - .expect("canonical message envelope"); - let payload = serde_json::to_value(envelope).expect("canonical payload"); - let observation = DurableObservationV1::new( - ObservationIdentityMaterialV1::for_native_record( - source, - ObservationScopeV1::Profile, - ObservationSourceGenerationV1::new(1).expect("generation"), - range, - ObservationOrderingDomainV1::SnapshotOrder, - record_id, - ) - .expect("observation identity"), - SanitizationReceiptV1::new( - SanitizationReceiptRefV1::new( - SanitizationReceiptId::new(format!("receipt.page.{rank:02}")).expect("receipt id"), - tracedecay_domain::ComponentVersion::new("sanitizer.page-fixture.v1") - .expect("sanitizer"), - ) - .expect("receipt reference"), - SanitizerDispositionV1::Accepted, - SensitivityV1::NonSensitive, - Some(PayloadReferenceV1::for_payload(&payload).expect("payload reference")), - ) - .expect("receipt"), - RetentionClass::new("retention.page-fixture").expect("retention"), - payload, - ) - .expect("durable observation"); let store = database.observation_store(); - let previous_cursor = store - .get_source_cursor(observation.source(), observation.scope()) + let mut previous_cursor = store + .get_source_cursor(&source, &ObservationScopeV1::Profile) .await .expect("source cursor"); - let next_cursor = ObservationSourceCursorV1::for_ordering( - observation.source().clone(), - observation.scope().clone(), - observation.identity().generation(), - observation.identity().ordering_domain(), - observation.identity().position().end(), - ) - .expect("next cursor"); - let write = ObservationWrite::new(observation.clone(), previous_cursor, next_cursor) + let last_rank = rank; + let mut pending_writes = Vec::new(); + let mut fixtures = Vec::with_capacity(ranks.len()); + for rank in ranks { + let message_id = format!("message.page.{rank:02}"); + let text = format!("canonical content {rank}"); + let ordinal = u64::try_from(rank).expect("ordinal"); + let range = ObservationSourceRangeV1::new(ordinal, ordinal + 1).expect("source range"); + let record_id = ObservationId::new(format!("record.page.{rank:02}")).expect("record id"); + let projected_message_id = if provider == "claude" { + record_id.as_str().to_owned() + } else { + message_id.clone() + }; + let relations = CanonicalObservationRelationsV1::new(session.clone()) + .with_message_id(ObservationId::new(message_id.clone()).expect("message id")); + let envelope = CanonicalObservationEnvelopeV1::new( + provider_id.clone(), + "message", + record_id.clone(), + relations, + vec![CanonicalObservationFactV1::Message { + role: CanonicalMessageRoleV1::Assistant, + content: json!(text), + model: Some("fixture-model".to_owned()), + timestamp: Some(i64::try_from(rank).expect("timestamp")), + }], + CanonicalObservationEvidenceV1::new(ObservationOrderingDomainV1::SnapshotOrder, range), + ) + .expect("canonical message envelope"); + let payload = serde_json::to_value(envelope).expect("canonical payload"); + let observation = DurableObservationV1::new( + ObservationIdentityMaterialV1::for_native_record( + source.clone(), + ObservationScopeV1::Profile, + ObservationSourceGenerationV1::new(1).expect("generation"), + range, + ObservationOrderingDomainV1::SnapshotOrder, + record_id, + ) + .expect("observation identity"), + SanitizationReceiptV1::new( + SanitizationReceiptRefV1::new( + SanitizationReceiptId::new(format!("receipt.page.{rank:02}")) + .expect("receipt id"), + tracedecay_domain::ComponentVersion::new("sanitizer.page-fixture.v1") + .expect("sanitizer"), + ) + .expect("receipt reference"), + SanitizerDispositionV1::Accepted, + SensitivityV1::NonSensitive, + Some(PayloadReferenceV1::for_payload(&payload).expect("payload reference")), + ) + .expect("receipt"), + RetentionClass::new("retention.page-fixture").expect("retention"), + payload, + ) + .expect("durable observation"); + let next_cursor = ObservationSourceCursorV1::for_ordering( + observation.source().clone(), + observation.scope().clone(), + observation.identity().generation(), + observation.identity().ordering_domain(), + observation.identity().position().end(), + ) + .expect("next cursor"); + let write = ObservationWrite::new( + observation.clone(), + previous_cursor.take(), + next_cursor.clone(), + ) .expect("observation write"); - let projection_generation = - ProjectionGenerationId::new("projection.page-fixture.v1").expect("projection generation"); - let authorization = build_observation_resolution_authorization_v1( - write.observation(), - tracedecay_store::OBSERVATION_CAPTURE_AUTHORITY_V1, - ) - .expect("resolution authorization"); - let anchor = build_observation_retrieval_anchor( - write.observation(), - projection_generation.clone(), - UtcMicros(1), - authorization, - ) - .expect("retrieval anchor"); - store - .persist_observation( - AnchoredObservationWrite::new(write, anchor, projection_generation) - .expect("anchored observation"), + let projection_generation = ProjectionGenerationId::new("projection.page-fixture.v1") + .expect("projection generation"); + let authorization = build_observation_resolution_authorization_v1( + write.observation(), + tracedecay_store::OBSERVATION_CAPTURE_AUTHORITY_V1, ) - .await - .expect("persist canonical observation"); - store - .project_observation(observation.observation_id()) - .await - .expect("project canonical observation"); + .expect("resolution authorization"); + let anchor = build_observation_retrieval_anchor( + write.observation(), + projection_generation.clone(), + UtcMicros(1), + authorization, + ) + .expect("retrieval anchor"); + previous_cursor = Some(next_cursor); + let write = AnchoredObservationWrite::new(write, anchor, projection_generation) + .expect("anchored observation"); + if scalar { + store + .persist_observation(write) + .await + .expect("persist canonical observation"); + store + .project_observation(observation.observation_id()) + .await + .expect("project canonical observation"); + } else { + pending_writes.push(write); + if pending_writes.len() == BATCH_ITEMS || rank == last_rank { + let count = pending_writes.len(); + let outcomes = store + .persist_observations(std::mem::take(&mut pending_writes)) + .await + .expect("persist canonical observation page"); + assert_eq!(outcomes.len(), count); + let projected = store + .project_queued_observations(count) + .await + .expect("project canonical observation page") + .expect("registered batched projector"); + assert_eq!(projected.items.len(), count); + assert!( + !projected.has_more, + "fixture page must fully project before the next page" + ); + } + } + fixtures.push(RealPageFixture { + provider: provider.clone(), + session_id: session_id.clone(), + message_id, + projected_message_id, + text, + anchor_id: derive_exact_observation_anchor_id( + observation.scope(), + observation.observation_id(), + ) + .expect("canonical anchor id"), + active_generation: 0, + }); + } let active_generation = if finalize { database .lcm_protect_session_raw_messages(&provider, &session_id) @@ -312,19 +385,10 @@ async fn seed_real_page_fixture_in_session( 0 }; - RealPageFixture { - provider, - session_id, - message_id, - projected_message_id, - text, - anchor_id: derive_exact_observation_anchor_id( - observation.scope(), - observation.observation_id(), - ) - .expect("canonical anchor id"), - active_generation, + for fixture in &mut fixtures { + fixture.active_generation = active_generation; } + fixtures } #[tokio::test] @@ -1556,19 +1620,48 @@ async fn small_lookup_reads_a_session_larger_than_the_response_budget() { .await; let root = real_page_root("root.page"); let session_id = "session.page.large".to_owned(); - let mut expected_messages = BTreeMap::new(); - for rank in 0..RECORDS { - let fixture = seed_real_page_fixture_in_session( + let mut fixtures = seed_real_page_fixtures_in_session( + harness.registered.as_ref(), + &root, + 0..RECORDS - 1, + "codex".to_owned(), + session_id.clone(), + false, + ) + .await; + // The scalar fixture replaces the session row before its last projection; + // retain that final metadata instead of merging all message timestamps. + fixtures.push( + seed_real_page_fixture_in_session( harness.registered.as_ref(), &root, - rank, + RECORDS - 1, "codex".to_owned(), session_id.clone(), - rank + 1 == RECORDS, + true, ) - .await; - expected_messages.insert(fixture.projected_message_id, fixture.text); - } + .await, + ); + let stored_session = harness + .registered + .get_session("codex", &session_id) + .await + .expect("fixture session read") + .expect("fixture session"); + assert_eq!(stored_session.started_at, Some(1499)); + assert_eq!(stored_session.ended_at, Some(1499)); + assert_eq!( + stored_session.title.as_deref(), + Some("fixture session 1499") + ); + assert_eq!( + stored_session.project_path, + tracedecay_sessions::runtime::shared::durable_project_path_key("/fixture/1499"), + ); + let expected_messages = fixtures + .into_iter() + .map(|fixture| (fixture.projected_message_id, fixture.text)) + .collect::>(); let root = registered_profile_retrieval_root(&harness.registered); let scope = root .identity() diff --git a/crates/tracedecay/tests/dashboard_api_test/runtime.rs b/crates/tracedecay/tests/dashboard_api_test/runtime.rs index feb1c5cd12..e4620d38f4 100644 --- a/crates/tracedecay/tests/dashboard_api_test/runtime.rs +++ b/crates/tracedecay/tests/dashboard_api_test/runtime.rs @@ -22,6 +22,8 @@ use tracedecay_graph_query::{ VerifiedCodeGraphRead, }; use tracedecay_host_admission::session_ingest_authority::GlobalDbSessionIngestAuthority; +use tracedecay_lcm::payload::PayloadFileRollback; +use tracedecay_lcm::raw::{commit_staged_raw_message, stage_raw_message_with_payload_tracked}; use tracedecay_project::project::{TraceDecay, TraceDecayOpenOptions}; use tracedecay_project::test_support::host_admission::ensure_process_background_cpu_authority; use tracedecay_runtime_core::config::ProfileRoot; @@ -519,19 +521,46 @@ impl DashboardTestRuntimeV1 { operation: "seed dashboard test session message".to_owned(), message: "registered session database has no storage root".to_owned(), })?; - for message in messages { - database - .lcm_ingest_raw_message(storage_root, message) - .await + // Keep fixture write leases bounded like the host projection drain. + for chunk in messages.chunks(32) { + let mut rollback = PayloadFileRollback::begin_cancellation_safe(storage_root); + let staged = chunk + .iter() + .map(|message| { + stage_raw_message_with_payload_tracked(storage_root, message, &mut rollback) + }) + .collect::, _>>() .map_err(|error| TraceDecayError::Database { operation: "seed dashboard test session message".to_owned(), message: error.to_string(), })?; + let transaction = database.begin_write_transaction().await?; + for (message, staged) in chunk.iter().zip(staged) { + commit_staged_raw_message(&transaction, message, staged) + .await + .map_err(|error| TraceDecayError::Database { + operation: "seed dashboard test session message".to_owned(), + message: error.to_string(), + })?; + } + transaction + .commit() + .await + .map_err(|error| TraceDecayError::Database { + operation: "commit dashboard test session messages".to_owned(), + message: error.to_string(), + })?; + rollback.disarm(); } let mut store_ids = Vec::with_capacity(messages.len()); for message in messages { - let raw = load_registered_raw_message(database, &message.provider, &message.message_id) + let store_id = database + .lcm_raw_message_store_id(&message.provider, &message.message_id) .await + .map_err(|error| TraceDecayError::Database { + operation: "read dashboard test transcript store id".to_owned(), + message: error.to_string(), + })? .ok_or_else(|| TraceDecayError::Database { operation: "read dashboard test transcript store id".to_owned(), message: format!( @@ -539,7 +568,7 @@ impl DashboardTestRuntimeV1 { message.provider, message.message_id ), })?; - store_ids.push(raw.store_id); + store_ids.push(store_id); } Ok(store_ids) } @@ -687,20 +716,6 @@ fn dashboard_test_graph_error(operation: &str, error: impl Display) -> TraceDeca } } -async fn load_registered_raw_message( - database: &RegisteredGlobalDb, - provider: &str, - message_id: &str, -) -> Option { - let snapshot = database - .read_snapshot() - .await - .expect("dashboard test raw-message snapshot must remain registered"); - tracedecay_lcm::schema::load_raw_message(&snapshot, provider, message_id) - .await - .expect("dashboard test raw-message load must not hide database or receipt failure") -} - /// Gives the fixture root the same registered-repository identity a /// production project gains at registration: a real git repository whose /// common directory carries the authoritative repository identity marker. From 9e33cebd473cd08b968b42ea7ebd4f3908e3435b Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 14:28:25 -0700 Subject: [PATCH 10/29] fix(graph): serve source bodies without catalog rewarming --- .../production/extended_primitive.rs | 30 ++++-- .../src/project_reads.rs | 11 +- .../src/graph_projection.rs | 31 +++++- .../src/graph_projection/interactive.rs | 11 ++ .../tracedecay-graph-query/src/projection.rs | 8 ++ .../src/daemon/production_harness.rs | 3 + .../source_body_readiness_journey_test.rs | 100 ++++++++++++++++++ 7 files changed, 181 insertions(+), 13 deletions(-) create mode 100644 crates/tracedecay/src/daemon/production_harness/source_body_readiness_journey_test.rs diff --git a/crates/tracedecay-application/src/primitives/production/extended_primitive.rs b/crates/tracedecay-application/src/primitives/production/extended_primitive.rs index 3bf4ebfc27..c071819d76 100644 --- a/crates/tracedecay-application/src/primitives/production/extended_primitive.rs +++ b/crates/tracedecay-application/src/primitives/production/extended_primitive.rs @@ -5,7 +5,9 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, OnceLock, PoisonError}; use sha2::{Digest, Sha256}; -use tracedecay_code_index::graph_projection::{CodeGraphReadCostMeter, CodeGraphSymbolSummaryV1}; +use tracedecay_code_index::graph_projection::{ + CodeGraphReadCostMeter, CodeGraphReadinessRequirement, CodeGraphSymbolSummaryV1, +}; use tracedecay_contracts::retrieval::{ HealthDeltaRequest, HealthDeltaResult, PrimitiveFailureKind, RetrievalPortContext, SymbolPrimitiveRecord, @@ -567,14 +569,24 @@ impl ExtendedPrimitivePort for TraceDecayExtendedPrimitivePortV1 { Box::pin(tracing::Instrument::instrument( async move { let cancellation = request_graph_cancellation(context.request); - let reader = match open_code_graph( - self.code_graph.as_ref(), - context.request, - now_observed(), - Arc::clone(&cancellation), - ) - .await - { + let opened = self + .code_graph + .open( + CodeGraphReadRequest::new( + context.request, + now_observed(), + Arc::clone(&cancellation), + ) + .with_readiness(CodeGraphReadinessRequirement::Engine), + ) + .await; + let reader = match opened.and_then(|graph| { + graph.reader_with_cancellation( + context.request, + now_observed(), + Arc::clone(&cancellation), + ) + }) { Ok(reader) => reader, Err(error) => { return graph_read_outcome(&error, EvidenceDomain::Source, now_observed()); diff --git a/crates/tracedecay-code-index-runtime/src/project_reads.rs b/crates/tracedecay-code-index-runtime/src/project_reads.rs index 622711a1a8..9a885ffedf 100644 --- a/crates/tracedecay-code-index-runtime/src/project_reads.rs +++ b/crates/tracedecay-code-index-runtime/src/project_reads.rs @@ -60,12 +60,19 @@ fn refuse_projection_wait(request: &CodeGraphReadRequest<'_>) -> Result<(), Code /// the read waits for it within what is left of its budget, so a re-warm /// that finishes in time serves the read instead of refusing it. Past the /// budget it answers the measured warm-up still needed. +#[tracing::instrument( + name = "daemon.code_index.read.await_rewarm", + level = "trace", + skip_all, + fields(readiness = ?request.readiness) +)] async fn await_rewarm( store: &Arc, request: &CodeGraphReadRequest<'_>, ) -> Result<(), CodeGraphReadError> { // A zero budget never blocks: it starts the re-warm and reports it. - if store.await_rewarm(Duration::ZERO).is_ok() { + let requirement = request.readiness; + if store.await_rewarm_for(Duration::ZERO, requirement).is_ok() { return Ok(()); } let now = now_micros(); @@ -81,7 +88,7 @@ async fn await_rewarm( let budget = Duration::from_micros(u64::try_from(expires_at.0.saturating_sub(now.0)).unwrap_or(0)); let waiting = Arc::clone(store); - let wait = tokio::task::spawn_blocking(move || waiting.await_rewarm(budget)); + let wait = tokio::task::spawn_blocking(move || waiting.await_rewarm_for(budget, requirement)); let settled = match request.live_cancellation { Some(signal) => tokio::select! { biased; diff --git a/crates/tracedecay-code-index/src/graph_projection.rs b/crates/tracedecay-code-index/src/graph_projection.rs index d85a15b945..9e6776f827 100644 --- a/crates/tracedecay-code-index/src/graph_projection.rs +++ b/crates/tracedecay-code-index/src/graph_projection.rs @@ -91,6 +91,15 @@ const ADJACENCY_SEED_CHUNK: usize = 4_096; /// v12 stores oversized records as bounded DEFLATE bytes, preserving every field. pub const CODE_GRAPH_PROJECTOR_REVISION: &str = "code-graph-projector.v12"; +/// Resident graph data a read actually needs before opening its reader. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum CodeGraphReadinessRequirement { + /// Occurrence-seeded point reads do not consult the interactive catalog. + Engine, + /// Name, file, and census reads need the engine and interactive catalog. + Catalog, +} + /// Every semantic edge kind, at its [`relation_edge_kind_index`]. const RELATION_EDGE_KINDS: [RelationEdgeKindV1; 9] = [ RelationEdgeKindV1::Calls, @@ -602,11 +611,20 @@ impl CodeGraphProjectionStore { /// once when nothing released is warming; the reader then answers the /// store's state as before. pub fn await_rewarm(&self, budget: Duration) -> Result<(), CodeGraphRewarmPendingV1> { + self.await_rewarm_for(budget, CodeGraphReadinessRequirement::Catalog) + } + + /// Waits only for the resident data required by this read. + pub fn await_rewarm_for( + &self, + budget: Duration, + requirement: CodeGraphReadinessRequirement, + ) -> Result<(), CodeGraphRewarmPendingV1> { let started = Instant::now(); let mut woke = false; loop { let epoch = self.warm_clock.epoch(); - let Some(pending) = self.rewarm_in_flight(woke) else { + let Some(pending) = self.rewarm_in_flight(woke, requirement) else { return Ok(()); }; let left = budget.saturating_sub(started.elapsed()); @@ -622,7 +640,11 @@ impl CodeGraphProjectionStore { /// measured warm-up it still needs; `None` when no released owner is /// warming. An engine still cold after a settled warm failed it, which /// the reader answers. - fn rewarm_in_flight(&self, woke: bool) -> Option { + fn rewarm_in_flight( + &self, + woke: bool, + requirement: CodeGraphReadinessRequirement, + ) -> Option { let engine = match self.snapshot.serving_engine_resident() { Ok(true) => None, Ok(false) if woke || !self.released.load(AtomicOrdering::Acquire) => return None, @@ -632,6 +654,11 @@ impl CodeGraphProjectionStore { } Err(_) => return None, }; + if requirement == CodeGraphReadinessRequirement::Engine { + return engine.map(|remaining| CodeGraphRewarmPendingV1 { + retry_after: remaining.max(Duration::from_millis(1)), + }); + } let catalog = match (self.interactive_catalog.residency(), engine) { (CatalogResidency::Resident | CatalogResidency::Unreleased, None) => return None, (CatalogResidency::Resident | CatalogResidency::Unreleased, Some(_)) => Duration::ZERO, diff --git a/crates/tracedecay-code-index/src/graph_projection/interactive.rs b/crates/tracedecay-code-index/src/graph_projection/interactive.rs index f012e95810..8053446ec4 100644 --- a/crates/tracedecay-code-index/src/graph_projection/interactive.rs +++ b/crates/tracedecay-code-index/src/graph_projection/interactive.rs @@ -272,6 +272,17 @@ impl CodeGraphReadCostMeter { } impl CodeGraphProjectionStore { + /// Holds the existing catalog-build gate while a fixture drives real reads. + #[cfg(any(test, feature = "test-helpers"))] + pub fn hold_catalog_build_for_test( + &self, + ) -> Result, CodeGraphProjectionError> { + self.interactive_catalog + .build + .lock() + .map_err(|_| catalog_lock_poisoned()) + } + /// Builds and validates the generation-pinned interactive catalog before /// serving latency-bounded reads. Only a fully built immutable catalog is /// published into the store's shared slot. diff --git a/crates/tracedecay-graph-query/src/projection.rs b/crates/tracedecay-graph-query/src/projection.rs index 26a6bb12de..d5c165b781 100644 --- a/crates/tracedecay-graph-query/src/projection.rs +++ b/crates/tracedecay-graph-query/src/projection.rs @@ -7,6 +7,7 @@ use std::sync::Arc; use thiserror::Error; use tracedecay_code_index::graph_projection::{ CodeGraphInteractiveReader, CodeGraphProjectionError, CodeGraphProjectionStore, + CodeGraphReadinessRequirement, }; pub use tracedecay_contracts::retrieval::CodeGraphReadFreshnessV1; use tracedecay_contracts::{ @@ -67,6 +68,7 @@ pub struct CodeGraphReadRequest<'a> { pub context: &'a RequestContext, pub observed_at: UtcMicros, pub cancellation: Arc, + pub readiness: CodeGraphReadinessRequirement, pub deadline: Option, pub live_cancellation: Option<&'a CancellationSignal>, } @@ -81,11 +83,17 @@ impl<'a> CodeGraphReadRequest<'a> { context, observed_at, cancellation, + readiness: CodeGraphReadinessRequirement::Catalog, deadline: None, live_cancellation: None, } } + pub fn with_readiness(mut self, readiness: CodeGraphReadinessRequirement) -> Self { + self.readiness = readiness; + self + } + pub fn with_deadline(mut self, deadline: Deadline) -> Self { self.deadline = Some(deadline); self diff --git a/crates/tracedecay/src/daemon/production_harness.rs b/crates/tracedecay/src/daemon/production_harness.rs index cdae3b7619..44e875834d 100644 --- a/crates/tracedecay/src/daemon/production_harness.rs +++ b/crates/tracedecay/src/daemon/production_harness.rs @@ -1729,3 +1729,6 @@ mod lcm_preserved_profile_journey_test; #[cfg(all(test, target_os = "linux", target_env = "gnu"))] mod idle_history_reads_journey_test; + +#[cfg(test)] +mod source_body_readiness_journey_test; diff --git a/crates/tracedecay/src/daemon/production_harness/source_body_readiness_journey_test.rs b/crates/tracedecay/src/daemon/production_harness/source_body_readiness_journey_test.rs new file mode 100644 index 0000000000..acb9b21475 --- /dev/null +++ b/crates/tracedecay/src/daemon/production_harness/source_body_readiness_journey_test.rs @@ -0,0 +1,100 @@ +use std::sync::{Arc, mpsc}; +use std::time::Duration; + +use tempfile::TempDir; +use tracedecay_code_index::graph_projection::CodeGraphCatalogReleaseV1; +use tracedecay_domain::{ProjectId, SymbolOccurrenceId, UtcMicros}; +use tracedecay_graph_db::NeverCancelled; + +use super::ProductionProjectCompositionHarnessV1; +use super::journey_test_support::{git, tool_payload}; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn source_body_serves_while_the_released_catalog_cannot_rewarm() { + let isolation = TempDir::new().unwrap(); + let project = isolation.path().join("project"); + std::fs::create_dir_all(&project).unwrap(); + let source = "pub fn source_anchor() -> u32 { 7 }"; + std::fs::write(project.join("lib.rs"), source).unwrap(); + git(&project, &["init", "-q", "-b", "main"]); + git(&project, &["add", "."]); + git(&project, &["commit", "-qm", "seed source body"]); + let harness = ProductionProjectCompositionHarnessV1::open(isolation.path(), [project.clone()]) + .await + .unwrap(); + let project_id = ProjectId::new(harness.project_id(&project).await.unwrap()).unwrap(); + let scope = + tracedecay_code_index_runtime::resolved_scope_for_project(&project, &project_id).unwrap(); + let serving = harness + .resources + .as_ref() + .unwrap() + .invocation + .code_index_schedulers + .latest_text_serving_for_scope(&scope) + .await + .unwrap(); + let store = serving.interactive_graph_store().unwrap(); + store + .warm_interactive_catalog_with_cancellation(None, Arc::new(NeverCancelled)) + .unwrap(); + let lookup = tool_payload( + &harness + .call_tool( + &project, + "tracedecay_find_exact_symbol", + serde_json::json!({"name": "source_anchor", "format": "json"}), + ) + .await + .unwrap(), + ); + let node_id = lookup["matches"][0]["id"].as_str().unwrap(); + let occurrence = SymbolOccurrenceId::new(node_id).unwrap(); + let reader = store + .interactive_reader_with_cancellation(store.generation(), Arc::new(NeverCancelled)) + .unwrap(); + assert!(matches!( + store.release_interactive_catalog(), + CodeGraphCatalogReleaseV1::Released { .. } + )); + let held_store = Arc::clone(&store); + let (held_tx, held_rx) = tokio::sync::oneshot::channel(); + let (release_tx, release_rx) = mpsc::channel::<()>(); + let holding = std::thread::spawn(move || { + let _guard = held_store.hold_catalog_build_for_test().unwrap(); + held_tx.send(()).unwrap(); + let _ = release_rx.recv(); + }); + held_rx.await.unwrap(); + // These point reads prove that the retained engine and occurrence are ready. + let point = reader.symbol_summary(&occurrence, Arc::new(NeverCancelled)); + let catalog_pending = store.await_rewarm(Duration::ZERO); + let deadline = UtcMicros(tracedecay_contracts::now_micros().0 + 1_000_000); + let response = harness + .call_tool_with_deadline( + &project, + "tracedecay_source_body", + serde_json::json!({"node_id": node_id, "format": "json"}), + deadline, + ) + .await; + let still_pending = store.await_rewarm(Duration::ZERO); + drop(release_tx); + holding.join().unwrap(); + let warmed = store.await_rewarm(Duration::from_secs(5)); + drop(reader); + drop(store); + drop(serving); + harness.shutdown().await; + + assert!(point.unwrap().is_some()); + assert!(catalog_pending.is_err()); + assert!( + still_pending.is_err(), + "source read must finish before the held catalog warms" + ); + assert_eq!(warmed, Ok(())); + let payload = tool_payload(&response.unwrap()); + assert_eq!(payload["outcome"]["outcome"], "evidence", "{payload}"); + assert_eq!(payload["outcome"]["value"]["payload"]["body"], source); +} From fda50081ed639f58815ef55db210f806bd4c0ce2 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 14:31:03 -0700 Subject: [PATCH 11/29] fix(bazel): verify packaged CLI through workspace test targets --- .github/workflows/distribution-acceptance.yml | 6 +++ scripts/bazel/rust_test.bzl | 5 +++ scripts/check-distribution-acceptance.sh | 40 +++++++++++++------ 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/.github/workflows/distribution-acceptance.yml b/.github/workflows/distribution-acceptance.yml index 0d0ef000c7..1817ee89d0 100644 --- a/.github/workflows/distribution-acceptance.yml +++ b/.github/workflows/distribution-acceptance.yml @@ -35,6 +35,7 @@ jobs: timeout-minutes: 240 env: TARGET: x86_64-unknown-linux-gnu + AST_GREP_VERSION: "0.44.0" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -46,6 +47,11 @@ jobs: - uses: ./.github/actions/setup-pnpm + - uses: ./.github/actions/setup-bazel + with: + cache-key: distribution-acceptance-linux + test-tools: "true" + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.14.8" diff --git a/scripts/bazel/rust_test.bzl b/scripts/bazel/rust_test.bzl index 75e71985b2..934530c5f8 100644 --- a/scripts/bazel/rust_test.bzl +++ b/scripts/bazel/rust_test.bzl @@ -96,13 +96,18 @@ def _isolated_rust_test_impl(ctx): } for variable, file in {"CARGO": toolchain.cargo, "RUSTC": toolchain.rustc, "RUSTDOC": toolchain.rust_doc}.items(): paths[variable] = _runfiles_path(file, workspace) + # Distribution acceptance explicitly supplies its extracted CLI through + # --test_env. Every other runfile, and the default CLI, stays declared here. if windows: runfiles_env = "\n".join([ + ('if not defined {} '.format(variable) if variable == "TRACEDECAY_TEST_BIN" else "") + 'set "{}=%TEST_SRCDIR%/{}"'.format(variable, path) for variable, path in paths.items() ]) else: runfiles_env = " ".join([ + '"{}=${{{}:-$TEST_SRCDIR/{}}}"'.format(variable, variable, path) + if variable == "TRACEDECAY_TEST_BIN" else '"{}=$TEST_SRCDIR/{}"'.format(variable, path) for variable, path in paths.items() ]) diff --git a/scripts/check-distribution-acceptance.sh b/scripts/check-distribution-acceptance.sh index bbd1b9c701..52e9e81568 100755 --- a/scripts/check-distribution-acceptance.sh +++ b/scripts/check-distribution-acceptance.sh @@ -688,21 +688,35 @@ cargo nextest run \ # graph. Query, root-library, and LSP source suites run in exhaustive Linux CI; # rerunning them here added no archive assertion. The MCP harness stays focused # on the modules that spawn TRACEDECAY_TEST_BIN, so it proves the packaged CLI -# without rerunning all 573 source-library tests. Run it from the verified -# checkout so unchanged source-graph units retain their original Cargo paths. +# without rerunning unrelated source-library tests. Run it from the verified +# checkout through the canonical isolated Bazel launcher. echo "distribution acceptance: checking packaged CLI integration behavior" resolve_clean_source_head "$repo" "$source_git_sha" >/dev/null -TRACEDECAY_TEST_BIN="$packaged_cli_bin" \ - CARGO_NET_OFFLINE=true cargo nextest run \ - --manifest-path "$repo/Cargo.toml" \ - --release \ - -p tracedecay \ - --test mcp_suite \ - --features tracedecay/test-transport \ - --no-fail-fast \ - --retries 2 \ - -E 'test(/^mcp_cli_serve_test::/) | test(/^serve_template_path_test::/) | test(=mcp_handler_test::lcm_test::lcm_status_cli_bridge_accepts_json_args)' \ - --no-tests=fail +# The extracted binary is outside Bazel's declared runfiles. Run locally and +# disable result caching so its current bytes are exercised on every invocation. +# Check each filter independently: libtest otherwise accepts zero matches. +require_command bazel +for filter in \ + 'mcp_cli_serve_test::' \ + 'serve_template_path_test::' \ + 'mcp_handler_test::lcm_test::lcm_status_cli_bridge_accepts_json_args'; do + ( + cd "$repo" + test_args=(--test_arg="$filter") + expected_count=nonzero + if [[ $filter == mcp_handler_test::* ]]; then + test_args+=(--test_arg=--exact) + expected_count=1 + fi + REQUIRE_EXACT_TEST_COUNT="$expected_count" scripts/require-exact-test.sh \ + bazel test --config=ci //crates/tracedecay:mcp_suite \ + --test_strategy=standalone \ + --nocache_test_results \ + --test_output=all \ + --test_env="TRACEDECAY_TEST_BIN=$packaged_cli_bin" \ + "${test_args[@]}" + ) +done resolve_clean_source_head "$repo" "$source_git_sha" >/dev/null install_root="$work/install" From 896e9ddf22510ca0e6c77cb447c424c938a45b47 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 14:31:52 -0700 Subject: [PATCH 12/29] test(sessions): remove redundant source generation clone --- crates/tracedecay-global-db/src/tests/source_rewrite.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/tracedecay-global-db/src/tests/source_rewrite.rs b/crates/tracedecay-global-db/src/tests/source_rewrite.rs index 4128881a6e..8da3717511 100644 --- a/crates/tracedecay-global-db/src/tests/source_rewrite.rs +++ b/crates/tracedecay-global-db/src/tests/source_rewrite.rs @@ -48,7 +48,7 @@ async fn rewrite_completion_retires_unoffered_records_beyond_the_query_cap() { harness .registered - .begin_observation_source_rewrite(&source, &scope, previous, generation.clone(), 0) + .begin_observation_source_rewrite(&source, &scope, previous, generation, 0) .await .unwrap(); From 6f3f2c7fa861a2bf448c9e93356e9db1bd1cc229 Mon Sep 17 00:00:00 2001 From: "zackary.l.jackson" Date: Thu, 8 Oct 2026 21:32:56 +0000 Subject: [PATCH 13/29] test(runtime): cover real retirement and rollback in source rewrite Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/tests/source_rewrite.rs | 242 +++++++++++++++++- 1 file changed, 238 insertions(+), 4 deletions(-) diff --git a/crates/tracedecay-global-db/src/tests/source_rewrite.rs b/crates/tracedecay-global-db/src/tests/source_rewrite.rs index 4128881a6e..9881d2a643 100644 --- a/crates/tracedecay-global-db/src/tests/source_rewrite.rs +++ b/crates/tracedecay-global-db/src/tests/source_rewrite.rs @@ -7,12 +7,20 @@ //! read failed deterministically for exactly those sources and wedged the //! host catch-up in a retry loop that never converged. +use tempfile::TempDir; + use tracedecay_domain::{ - ObservationScopeV1, ObservationSourceGenerationV1, ObservationSourceIdentityV1, SessionId, + ObservationScopeV1, ObservationSourceGenerationV1, ObservationSourceIdentityV1, ProviderId, + SessionId, }; -use tracedecay_runtime_core::db::engine::params; +use tracedecay_runtime_core::db::engine::{QueryExecutor, params}; +use tracedecay_store::{ProjectionSkipReason, ProjectionStoreError}; -use crate::tests::harness::RegisteredGlobalDbHarness; +use crate::ObservationSourcePresenceV1; +use crate::tests::harness::{ + HostAdmissionScope, HostAdmissionTestRuntimeV1, RegisteredGlobalDbHarness, + seed_projected_messages, +}; /// More unoffered records than one keyset page *and* more than the engine's /// per-query materialization cap, so the test fails against the unbounded @@ -48,7 +56,7 @@ async fn rewrite_completion_retires_unoffered_records_beyond_the_query_cap() { harness .registered - .begin_observation_source_rewrite(&source, &scope, previous, generation.clone(), 0) + .begin_observation_source_rewrite(&source, &scope, previous, generation, 0) .await .unwrap(); @@ -86,3 +94,229 @@ async fn rewrite_completion_retires_unoffered_records_beyond_the_query_cap() { let rewrites: i64 = row.get(0).unwrap(); assert_eq!(rewrites, 0, "completed rewrite row must be removed"); } + +async fn table_count(conn: &impl QueryExecutor, sql: &str) -> i64 { + let mut rows = conn.query(sql, ()).await.unwrap(); + let row = rows.next().await.unwrap().unwrap(); + row.get(0).unwrap() +} + +/// Retiring an unoffered record is real work, not a presence-row cleanup: +/// its provenance and workflow facts are removed, a `source_record_retired` +/// disposition takes their place, and a sole-owned projected output is +/// deleted. A completion that dies mid-transaction must roll every earlier +/// retirement back; the pending rewrite survives, so a retry after the +/// failure clears converges — the retry loop the paged read exists to keep +/// alive would otherwise stay wedged. +/// +/// The fixture plants an inconsistent provenance row (a surviving co-owner +/// that claims an output its durable record does not derive) on the +/// record the keyset order retires last, so the first attempt retires real +/// records across pages and then fails inside +/// `retire_source_record` with `ProvenanceCollision`. Removing the planted +/// row and completing again retires every unoffered record for real. +#[tokio::test] +async fn rewrite_completion_retires_projected_records_and_rolls_back_a_failed_attempt() { + // More real projected records than one keyset page, so a failed attempt + // leaves proven retirements to roll back on every page. + const RECORDS: usize = 1_005; + + let directory = TempDir::new().unwrap(); + let runtime = HostAdmissionTestRuntimeV1::profile(directory.path()) + .await + .unwrap(); + let registered = runtime + .registered_database(HostAdmissionScope::Profile) + .expect("registered profile database"); + let seeded = seed_projected_messages(&runtime, 0..RECORDS).await; + + let source = ObservationSourceIdentityV1::for_provider( + ProviderId::new("codex").unwrap(), + SessionId::new("session.audit-batch").unwrap(), + ) + .unwrap(); + let scope = ObservationScopeV1::Profile; + let previous = ObservationSourceGenerationV1::new(1).unwrap(); + let generation = ObservationSourceGenerationV1::new(2).unwrap(); + + let offered: Vec = seeded + .iter() + .map(|(observation, _)| ObservationSourcePresenceV1 { + observation_id: observation.observation_id().clone(), + generation: previous, + start_offset: observation.identity().position().start(), + }) + .collect(); + registered + .record_observation_source_presence(&source, &scope, &offered) + .await + .unwrap(); + registered + .begin_observation_source_rewrite(&source, &scope, previous, generation, 0) + .await + .unwrap(); + + // The rewrite re-offers the first record under the new generation; its + // presence, projection, and outputs must survive completion untouched. + let survivor = &seeded[0].0; + registered + .record_observation_source_presence( + &source, + &scope, + &[ObservationSourcePresenceV1 { + observation_id: survivor.observation_id().clone(), + generation, + start_offset: survivor.identity().position().start(), + }], + ) + .await + .unwrap(); + + // The unoffered record the keyset order retires last, plus the output + // its projection owns. + let collided_index = seeded + .iter() + .enumerate() + .skip(1) + .max_by(|left, right| { + left.1 + .0 + .observation_id() + .as_str() + .cmp(right.1.0.observation_id().as_str()) + }) + .map(|(index, _)| index) + .unwrap(); + let collided_message_id = format!("message.record.audit-batch-{collided_index}"); + + // Plant a provenance row making the re-offered record co-own the last + // record's output. When the last record retires, the surviving owner is + // read back and its stored rendering derives a different message, which + // the retire path refuses with `ProvenanceCollision`. + let writer = registered.writer_connection().unwrap(); + writer + .execute( + "INSERT INTO observation_projection_provenance ( + projector_version, observation_id, output_ordinal, receipt_id, + output_provider, output_message_id, output_digest, message_created, + retrieval_anchor_id + ) + SELECT projector_version, observation_id, 1, receipt_id, + output_provider, ?1, output_digest, message_created, + retrieval_anchor_id + FROM observation_projection_provenance + WHERE observation_id = ?2", + params![ + collided_message_id.as_str(), + survivor.observation_id().as_str() + ], + ) + .await + .unwrap(); + + let error = registered + .complete_observation_source_rewrite(&source, &scope, generation) + .await + .unwrap_err(); + assert!( + matches!(error, ProjectionStoreError::ProvenanceCollision), + "colliding output ownership must fail completion, got {error:?}" + ); + + // Every retirement the attempt already performed must be rolled back: + // provenance and projected outputs restored, no dispositions recorded, + // and the pending rewrite still waiting for a converging retry. + assert_eq!( + table_count( + &writer, + "SELECT COUNT(*) FROM observation_projection_dispositions" + ) + .await, + 0, + "retirements before the collision must roll back with the transaction" + ); + assert_eq!( + table_count( + &writer, + "SELECT COUNT(*) FROM observation_projection_provenance" + ) + .await, + i64::try_from(RECORDS).unwrap() + 1, + "rolled-back retirements must restore provenance" + ); + assert_eq!( + table_count(&writer, "SELECT COUNT(*) FROM lcm_raw_messages").await, + i64::try_from(RECORDS).unwrap(), + "rolled-back retirements must restore projected outputs" + ); + assert_eq!( + table_count(&writer, "SELECT COUNT(*) FROM observation_source_rewrites").await, + 1, + "the failed attempt must leave the rewrite pending" + ); + + writer + .execute( + "DELETE FROM observation_projection_provenance + WHERE observation_id = ?1 AND output_ordinal = 1", + params![survivor.observation_id().as_str()], + ) + .await + .unwrap(); + + let retired = registered + .complete_observation_source_rewrite(&source, &scope, generation) + .await + .unwrap_or_else(|error| { + panic!("retry after clearing the collision must converge: {error}") + }); + assert_eq!(retired, u64::try_from(RECORDS - 1).unwrap()); + + let retired_reason = ProjectionSkipReason::SourceRecordRetired.as_str(); + assert_eq!( + table_count( + &writer, + &format!( + "SELECT COUNT(*) FROM observation_projection_dispositions + WHERE reason = '{retired_reason}'" + ) + ) + .await, + i64::try_from(RECORDS - 1).unwrap(), + "every unoffered record must take a source_record_retired disposition" + ); + assert_eq!( + table_count( + &writer, + "SELECT COUNT(*) FROM observation_projection_provenance" + ) + .await, + 1, + "only the re-offered record's provenance survives" + ); + assert_eq!( + table_count(&writer, "SELECT COUNT(*) FROM lcm_raw_messages").await, + 1, + "only the re-offered record's projected output survives" + ); + assert_eq!( + table_count(&writer, "SELECT COUNT(*) FROM observation_source_presence").await, + 1, + "only the re-offered record's presence row survives" + ); + assert_eq!( + table_count(&writer, "SELECT COUNT(*) FROM observation_source_rewrites").await, + 0, + "completed rewrite row must be removed" + ); + assert_eq!( + table_count( + &writer, + "SELECT COUNT(*) FROM session_temporal_resets + WHERE session_id = 'session.audit-batch'" + ) + .await, + 1, + "retired records must request their session's temporal rebuild" + ); +} From 59fdad2509671159fe5e5d320364c3e601b21646 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 14:43:51 -0700 Subject: [PATCH 14/29] fix(hooks): prepare spool durability before binding publication --- .../src/hooks/dispatch.rs | 26 ++++++---- .../src/hooks/dispatch/tests.rs | 15 ++++++ crates/tracedecay-hooks/src/spool/mod.rs | 19 +++++++ crates/tracedecay-hooks/src/spool/tests.rs | 52 +++++++++++++++++++ 4 files changed, 102 insertions(+), 10 deletions(-) diff --git a/crates/tracedecay-agent-hosts/src/hooks/dispatch.rs b/crates/tracedecay-agent-hosts/src/hooks/dispatch.rs index ce9fa7d031..74dc001ce3 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/dispatch.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/dispatch.rs @@ -151,20 +151,26 @@ pub fn publish_daemon_bindings( // admission waits behind that cold filesystem work. A live callback // already appending to a prepared spool is a legitimate peer writer, // so publication waits the same bounded budget instead of failing. - drop( - tracedecay_hooks::HookSpoolV1::open_within( - tracedecay_hooks::hook_v2_spool_root(&layout.data_root, *host), - tracedecay_hooks::HookSpoolConfigV1::stock(*host), - now, - tracedecay_hooks::HOOK_SYNCHRONOUS_BUDGET, - ) + let (capture, _) = tracedecay_hooks::HookSpoolV1::open_within( + tracedecay_hooks::hook_v2_spool_root(&layout.data_root, *host), + tracedecay_hooks::HookSpoolConfigV1::stock(*host), + now, + tracedecay_hooks::HOOK_SYNCHRONOUS_BUDGET, + ) + .map_err(|error| tracedecay_domain::errors::TraceDecayError::Config { + message: format!( + "failed to prepare {} Hook capture spool: {error}", + host.hook_key() + ), + })?; + capture + .prepare() .map_err(|error| tracedecay_domain::errors::TraceDecayError::Config { message: format!( - "failed to prepare {} Hook capture spool: {error}", + "failed to prepare {} Hook capture durability: {error}", host.hook_key() ), - })?, - ); + })?; drop( tracedecay_hooks::HookDeliveryReceiptWriterV1::open_within( tracedecay_hooks::hook_delivery_receipt_spool_root(&layout.data_root, *host), diff --git a/crates/tracedecay-agent-hosts/src/hooks/dispatch/tests.rs b/crates/tracedecay-agent-hosts/src/hooks/dispatch/tests.rs index 41b27e63e0..53198d29db 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/dispatch/tests.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/dispatch/tests.rs @@ -856,6 +856,21 @@ fn binding_publication_waits_for_a_live_callback_holding_the_spool() { publish_daemon_bindings(&runtime, &layout).unwrap(); let host = NativeHostIdentityV1::ClaudeCode; + let capture_root = tracedecay_hooks::hook_v2_spool_root(&layout.data_root, host); + let (prepared, report) = + HookSpoolV1::open(&capture_root, HookSpoolConfigV1::stock(host), UtcMicros(1)).unwrap(); + assert_eq!((report.pending_records, report.next_sequence), (0, 1)); + let barriers = tracedecay_private_fs::framed_log::sync_latency::inject( + &capture_root, + std::time::Duration::ZERO, + ); + prepared.prepare().unwrap(); + assert_eq!( + barriers.syncs(), + 0, + "publication already prepared this exact file" + ); + drop(barriers); let held = std::time::Duration::from_millis(20); let (capture, _) = HookSpoolV1::open( tracedecay_hooks::hook_v2_spool_root(&layout.data_root, host), diff --git a/crates/tracedecay-hooks/src/spool/mod.rs b/crates/tracedecay-hooks/src/spool/mod.rs index e4392d0fad..663cb91b9f 100644 --- a/crates/tracedecay-hooks/src/spool/mod.rs +++ b/crates/tracedecay-hooks/src/spool/mod.rs @@ -583,6 +583,25 @@ impl HookSpoolV1 { Ok(record) } + /// Prepare the exact records file before publishing a daemon binding. + /// No event is appended: the empty file and its directory entry become + /// durable here so the first callback only commits its own record data. + #[tracing::instrument(name = "hooks.spool.prepare", level = "trace", skip_all)] + pub fn prepare(self) -> Result<(), HookSpoolError> { + self.ensure_healthy()?; + if self.recovery_required { + return Err(HookSpoolError::RecoveryRequired); + } + // append_frame writes its bytes verbatim through append_unsynced; + // an empty slice creates the file without inventing a frame/sequence. + append_frame(&records_path(&self.root), &[])?; + let revision = + records_file_revision(&self.root)?.ok_or(HookSpoolError::MetadataCorrupted)?; + let root = self.root.clone(); + drop(self); + commit_records(&root, revision.identity, revision.length, COMMIT_WAIT) + } + /// Release the writer lease, then make every frame this handle appended or /// deduplicated against durable. Concurrent committers share one sync of /// the records file, so the lease covers only writing frames; a caller diff --git a/crates/tracedecay-hooks/src/spool/tests.rs b/crates/tracedecay-hooks/src/spool/tests.rs index efdbe49b21..81e6866273 100644 --- a/crates/tracedecay-hooks/src/spool/tests.rs +++ b/crates/tracedecay-hooks/src/spool/tests.rs @@ -170,6 +170,58 @@ fn checkpoint_header_json_len(bytes: &[u8]) -> usize { ) as usize } +#[test] +fn preparation_commits_empty_records_and_reset_invalidates_its_extent() { + let root = TestDir::new("prepared-records"); + let (spool, report) = HookSpoolV1::open(&root.0, config(), UtcMicros(10)).unwrap(); + assert_eq!(report.next_sequence, 1); + spool.prepare().unwrap(); + let revision = records_file_revision(&root.0).unwrap().unwrap(); + assert_eq!(revision.length, 0); + assert_eq!( + CommitLockV1::acquire(&root.0, COMMIT_WAIT) + .unwrap() + .synced_through(revision.identity) + .unwrap(), + Some(0) + ); + let (mut spool, report) = HookSpoolV1::open(&root.0, config(), UtcMicros(11)).unwrap(); + assert_eq!((report.pending_records, report.next_sequence), (0, 1)); + let barriers = tracedecay_private_fs::framed_log::sync_latency::inject(&root.0, Duration::ZERO); + let record = spool + .append(envelope(1, 9), &binding(), UtcMicros(11)) + .unwrap(); + assert_eq!(record.sequence, 1); + spool.commit().unwrap(); + assert_eq!( + barriers.syncs(), + 1, + "prepared capture only syncs record data" + ); + drop(barriers); + + HookSpoolV1::reset(&root.0, config(), UtcMicros(12)).unwrap(); + assert_eq!( + CommitLockV1::acquire(&root.0, COMMIT_WAIT) + .unwrap() + .synced_through(revision.identity) + .unwrap(), + None, + "reset must invalidate the prepared file's extent" + ); + let (spool, report) = HookSpoolV1::open(&root.0, config(), UtcMicros(13)).unwrap(); + assert_eq!((report.pending_records, report.next_sequence), (0, 1)); + spool.prepare().unwrap(); + let revision = records_file_revision(&root.0).unwrap().unwrap(); + assert_eq!( + CommitLockV1::acquire(&root.0, COMMIT_WAIT) + .unwrap() + .synced_through(revision.identity) + .unwrap(), + Some(0) + ); +} + #[test] fn checksum_is_real_sha256() { assert_eq!( From 123f6a09ad098b865ac117a73d0d2df827077122 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 14:56:42 -0700 Subject: [PATCH 15/29] fix(hooks): bind prepared checkpoints to empty records --- crates/tracedecay-agent-hosts/src/hooks/dispatch/tests.rs | 4 ++++ crates/tracedecay-hooks/src/spool/mod.rs | 6 ++++++ crates/tracedecay-hooks/src/spool/tests.rs | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/crates/tracedecay-agent-hosts/src/hooks/dispatch/tests.rs b/crates/tracedecay-agent-hosts/src/hooks/dispatch/tests.rs index 53198d29db..8ba2311c55 100644 --- a/crates/tracedecay-agent-hosts/src/hooks/dispatch/tests.rs +++ b/crates/tracedecay-agent-hosts/src/hooks/dispatch/tests.rs @@ -860,6 +860,10 @@ fn binding_publication_waits_for_a_live_callback_holding_the_spool() { let (prepared, report) = HookSpoolV1::open(&capture_root, HookSpoolConfigV1::stock(host), UtcMicros(1)).unwrap(); assert_eq!((report.pending_records, report.next_sequence), (0, 1)); + assert!( + !report.checkpoint_rewritten, + "publication prepares the first callback's checkpoint" + ); let barriers = tracedecay_private_fs::framed_log::sync_latency::inject( &capture_root, std::time::Duration::ZERO, diff --git a/crates/tracedecay-hooks/src/spool/mod.rs b/crates/tracedecay-hooks/src/spool/mod.rs index 663cb91b9f..984c34ac7d 100644 --- a/crates/tracedecay-hooks/src/spool/mod.rs +++ b/crates/tracedecay-hooks/src/spool/mod.rs @@ -597,6 +597,12 @@ impl HookSpoolV1 { append_frame(&records_path(&self.root), &[])?; let revision = records_file_revision(&self.root)?.ok_or(HookSpoolError::MetadataCorrupted)?; + if self.observed_records_revision.is_none() { + // Opening a new spool checkpointed the absent records file. Bind + // that empty checkpoint to the file just created, so the first + // callback can reuse it rather than rebuilding under its lease. + write_checkpoint(&self.root, self.config, &self.pending, &self.records_prefix)?; + } let root = self.root.clone(); drop(self); commit_records(&root, revision.identity, revision.length, COMMIT_WAIT) diff --git a/crates/tracedecay-hooks/src/spool/tests.rs b/crates/tracedecay-hooks/src/spool/tests.rs index 81e6866273..467da8adc0 100644 --- a/crates/tracedecay-hooks/src/spool/tests.rs +++ b/crates/tracedecay-hooks/src/spool/tests.rs @@ -187,6 +187,10 @@ fn preparation_commits_empty_records_and_reset_invalidates_its_extent() { ); let (mut spool, report) = HookSpoolV1::open(&root.0, config(), UtcMicros(11)).unwrap(); assert_eq!((report.pending_records, report.next_sequence), (0, 1)); + assert!( + !report.checkpoint_rewritten, + "first callback must reuse the prepared records checkpoint" + ); let barriers = tracedecay_private_fs::framed_log::sync_latency::inject(&root.0, Duration::ZERO); let record = spool .append(envelope(1, 9), &binding(), UtcMicros(11)) From 64b9b7670d2af7829cf9d3d14bbc0c6d8fd0f50d Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 15:34:39 -0700 Subject: [PATCH 16/29] fix(bazel): route formatting and generation through native targets --- .bazelrc | 4 ++ .github/workflows/ci.yml | 24 +++++---- .github/workflows/release-please.yml | 5 ++ BUILD.bazel | 1 + MODULE.bazel | 1 + benchmark_data/BUILD.bazel | 2 +- crates/tracedecay-agent-hosts/BUILD.bazel | 6 +-- crates/tracedecay-api/BUILD.bazel | 8 +-- crates/tracedecay-application/BUILD.bazel | 4 +- .../tracedecay-automation-runtime/BUILD.bazel | 2 +- crates/tracedecay-automation/BUILD.bazel | 2 +- crates/tracedecay-capture/BUILD.bazel | 4 +- crates/tracedecay-cli/BUILD.bazel | 18 +++---- crates/tracedecay-code-extraction/BUILD.bazel | 6 +-- .../BUILD.bazel | 2 +- .../tracedecay-code-index-runtime/BUILD.bazel | 2 +- crates/tracedecay-code-index/BUILD.bazel | 4 +- crates/tracedecay-configuration/BUILD.bazel | 2 +- crates/tracedecay-contracts/BUILD.bazel | 2 +- crates/tracedecay-daemon-control/BUILD.bazel | 2 +- crates/tracedecay-daemon-identity/BUILD.bazel | 2 +- crates/tracedecay-daemon-protocol/BUILD.bazel | 2 +- crates/tracedecay-daemon-service/BUILD.bazel | 6 +-- crates/tracedecay-dashboard-api/BUILD.bazel | 2 +- crates/tracedecay-domain/BUILD.bazel | 18 +++++-- crates/tracedecay-framing/BUILD.bazel | 2 +- crates/tracedecay-global-db/BUILD.bazel | 6 +-- crates/tracedecay-graph-db/BUILD.bazel | 14 ++--- crates/tracedecay-graph-query/BUILD.bazel | 2 +- crates/tracedecay-hooks/BUILD.bazel | 4 +- crates/tracedecay-host-admission/BUILD.bazel | 4 +- .../tracedecay-host-integration/BUILD.bazel | 2 +- crates/tracedecay-lcm/BUILD.bazel | 2 +- crates/tracedecay-lsp/BUILD.bazel | 2 +- crates/tracedecay-maintenance/BUILD.bazel | 2 +- crates/tracedecay-mcp-catalog/BUILD.bazel | 2 +- crates/tracedecay-mcp/BUILD.bazel | 14 ++--- crates/tracedecay-policy/BUILD.bazel | 2 +- crates/tracedecay-privacy/BUILD.bazel | 4 +- crates/tracedecay-private-fs/BUILD.bazel | 6 +-- crates/tracedecay-project/BUILD.bazel | 2 +- crates/tracedecay-query/BUILD.bazel | 8 +-- crates/tracedecay-runtime-core/BUILD.bazel | 10 ++-- .../tracedecay-rusqlite-runtime/BUILD.bazel | 6 +-- crates/tracedecay-sdk/BUILD.bazel | 6 +-- crates/tracedecay-search-eval/BUILD.bazel | 2 +- crates/tracedecay-session-memory/BUILD.bazel | 2 +- crates/tracedecay-session-runtime/BUILD.bazel | 22 ++++---- .../BUILD.bazel | 2 +- crates/tracedecay-sessions/BUILD.bazel | 2 +- crates/tracedecay-source-edit/BUILD.bazel | 2 +- crates/tracedecay-store-runtime/BUILD.bazel | 2 +- crates/tracedecay-store/BUILD.bazel | 2 +- crates/tracedecay-temporal-query/BUILD.bazel | 2 +- crates/tracedecay-tool-catalog/BUILD.bazel | 2 +- crates/tracedecay/BUILD.bazel | 52 +++++++++++-------- scripts/BUILD.bazel | 1 + scripts/bazel/gen_builds.py | 36 +++++++++---- ...-neutralize-release-pr-closing-keywords.py | 6 +++ scripts/update-release-pr-lockfile.sh | 2 +- tests/BUILD.bazel | 14 ++++- 61 files changed, 225 insertions(+), 157 deletions(-) mode change 100644 => 100755 scripts/bazel/gen_builds.py diff --git a/.bazelrc b/.bazelrc index 72ec4bc111..62cceb8f0c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -63,6 +63,10 @@ build:clippy --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect build:clippy --output_groups=clippy_checks build:clippy --@rules_rust//rust/settings:clippy_flag=-Dwarnings +build:fmt --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect +build:fmt --output_groups=rustfmt_checks +build:fmt --@rules_rust//rust/settings:rustfmt.toml=//:rustfmt.toml + # The shipped binary: Cargo's release profile (opt-level 3, no debug # assertions). Externals return to opt-level 3 over the perf overrides below. build:release --compilation_mode=opt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c584bede0..a7058b08f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,21 +189,25 @@ jobs: bash tests/release_pr_integrity_test.sh bash tests/dashboard_bundle_check_test.sh - # Judge formatting with the toolchain `rust-toolchain.toml` pins, not - # whatever `stable` is on the runner: rustfmt's output changes between - # releases (1.98.1 breaks a chain 1.97.1 keeps inline), and - # `dtolnay/rust-toolchain@stable` exports `RUSTUP_TOOLCHAIN`, which - # overrides the file. Installing through rustup with no override keeps - # the pin in one place. - - name: Install the repository's pinned toolchain - run: rustup show active-toolchain || rustup toolchain install + - uses: ./.github/actions/setup-bazel + with: + cache-key: ci-format-linux + - name: Check formatting - run: cargo fmt --all -- --check + shell: bash + run: | + set -euo pipefail + # The isolated test launchers do not expose Rust source providers. + # Explicit native labels include their private manual test binaries. + targets="$(bazel query 'kind("rust_(library|binary|test|proc_macro) rule", //...)')" + test -n "$targets" + mapfile -t rust_targets <<< "$targets" + bazel build --config=ci --config=fmt "${rust_targets[@]}" # Manifest edits that skip a gen_builds.py rerun leave stale BUILD # files; compare the committed files against a fresh generation. - name: Check generated Bazel BUILD files are current - run: python3 scripts/bazel/gen_builds.py --check + run: bazel run //scripts:bazel/gen_builds.py -- --check - name: Test and check dev skill host copies run: | diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 528f022951..ceab90b486 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -145,6 +145,11 @@ jobs: pnpm-lock.yaml Cargo.lock + - uses: ./.github/actions/setup-bazel + if: steps.release.outputs.prs_created == 'true' + with: + cache-key: release-metadata-ubuntu-latest + - name: Update release PR generated metadata if: steps.release.outputs.prs_created == 'true' env: diff --git a/BUILD.bazel b/BUILD.bazel index 0aceb3f83c..e205ae97b7 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -6,6 +6,7 @@ exports_files( [ "version.txt", "Cargo.toml", + "rustfmt.toml", "pnpm-lock.yaml", "pnpm-lock.bazel.yaml", ], diff --git a/MODULE.bazel b/MODULE.bazel index c6d55d09de..f5577cd098 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -32,6 +32,7 @@ rust.toolchain( "opt": "3", }, versions = ["1.97.1"], + rustfmt_version = "1.97.1", ) use_repo(rust, "rust_toolchains") register_toolchains("@rust_toolchains//:all") diff --git a/benchmark_data/BUILD.bazel b/benchmark_data/BUILD.bazel index 809b9b68ee..21276f5e9f 100644 --- a/benchmark_data/BUILD.bazel +++ b/benchmark_data/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py # BEGIN generated by scripts/bazel/gen_builds.py filegroup( diff --git a/crates/tracedecay-agent-hosts/BUILD.bazel b/crates/tracedecay-agent-hosts/BUILD.bazel index 1b69c1b40f..1287a149cb 100644 --- a/crates/tracedecay-agent-hosts/BUILD.bazel +++ b/crates/tracedecay-agent-hosts/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -91,7 +91,7 @@ cargo_build_script( rust_test( name = "context_scout_evidence", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/context_scout_evidence.rs", edition = crate_edition(), version = "0.1.0", @@ -106,7 +106,7 @@ rust_test( rust_test( name = "hooks_boundary", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/hooks_boundary.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-api/BUILD.bazel b/crates/tracedecay-api/BUILD.bazel index 483a0f18a6..c34f8206bf 100644 --- a/crates/tracedecay-api/BUILD.bazel +++ b/crates/tracedecay-api/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -44,7 +44,7 @@ rust_library( rust_test( name = "dashboard_presentations", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/dashboard_presentations.rs", edition = crate_edition(), version = "0.1.0", @@ -59,7 +59,7 @@ rust_test( rust_test( name = "handoff_routes", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/handoff_routes.rs", edition = crate_edition(), version = "0.1.0", @@ -74,7 +74,7 @@ rust_test( rust_test( name = "multi_root_read_model", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/multi_root_read_model.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-application/BUILD.bazel b/crates/tracedecay-application/BUILD.bazel index 9104752908..ddf35d088d 100644 --- a/crates/tracedecay-application/BUILD.bazel +++ b/crates/tracedecay-application/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -93,7 +93,7 @@ rust_test( rust_test( name = "pr_tracking", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/pr_tracking.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-automation-runtime/BUILD.bazel b/crates/tracedecay-automation-runtime/BUILD.bazel index af2e4385fb..753a05c8f9 100644 --- a/crates/tracedecay-automation-runtime/BUILD.bazel +++ b/crates/tracedecay-automation-runtime/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-automation/BUILD.bazel b/crates/tracedecay-automation/BUILD.bazel index 78fe2ba49f..ba85841323 100644 --- a/crates/tracedecay-automation/BUILD.bazel +++ b/crates/tracedecay-automation/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-capture/BUILD.bazel b/crates/tracedecay-capture/BUILD.bazel index 0de9dbc400..e02858f6ee 100644 --- a/crates/tracedecay-capture/BUILD.bazel +++ b/crates/tracedecay-capture/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -44,7 +44,7 @@ rust_library( rust_test( name = "canonical_envelope_allocations", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/canonical_envelope_allocations.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-cli/BUILD.bazel b/crates/tracedecay-cli/BUILD.bazel index 60c544dd71..059166a48c 100644 --- a/crates/tracedecay-cli/BUILD.bazel +++ b/crates/tracedecay-cli/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -85,7 +85,7 @@ rust_binary( rust_test( name = "c_library_allocator", - srcs = glob(["tests/**/*.rs"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common", "//tests:support"], + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common_sources", "//tests:support_sources"], crate_root = "tests/c_library_allocator.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -102,7 +102,7 @@ rust_test( rust_test( name = "core_cli_suite", - srcs = glob(["tests/core_cli_suite/**/*.rs"]) + glob(["build-support/**/*.rs", "tests/support/**/*.rs"]) + ["//crates/tracedecay:tests_common", "//tests:support"], + srcs = glob(["tests/core_cli_suite/**/*.rs"]) + glob(["build-support/**/*.rs", "tests/support/**/*.rs"]) + ["//crates/tracedecay:tests_common_sources", "//tests:support_sources"], crate_root = "tests/core_cli_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -119,7 +119,7 @@ rust_test( rust_test( name = "host_journeys_suite", - srcs = glob(["tests/host_journeys_suite/**/*.rs"]) + glob(["tests/support/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/host_journeys_suite/**/*.rs"]) + glob(["tests/support/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/host_journeys_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -136,7 +136,7 @@ rust_test( rust_test( name = "process_allocator_heaps", - srcs = glob(["tests/**/*.rs"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common", "//tests:support"], + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common_sources", "//tests:support_sources"], crate_root = "tests/process_allocator_heaps.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -153,7 +153,7 @@ rust_test( rust_test( name = "refresh_memory_journey", - srcs = glob(["tests/**/*.rs"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common", "//tests:support"], + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common_sources", "//tests:support_sources"], crate_root = "tests/refresh_memory_journey.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -170,7 +170,7 @@ rust_test( rust_test( name = "retained_parse_heap", - srcs = glob(["tests/**/*.rs"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common", "//tests:support"], + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common_sources", "//tests:support_sources"], crate_root = "tests/retained_parse_heap.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -187,7 +187,7 @@ rust_test( rust_test( name = "work_loop_journey", - srcs = glob(["tests/**/*.rs"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common", "//tests:support"], + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common_sources", "//tests:support_sources"], crate_root = "tests/work_loop_journey.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -204,7 +204,7 @@ rust_test( rust_test( name = "work_route_exposure_conformance", - srcs = glob(["tests/**/*.rs"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common", "//tests:support"], + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common_sources", "//tests:support_sources"], crate_root = "tests/work_route_exposure_conformance.rs", edition = crate_edition(), version = "1.0.0-beta.76", diff --git a/crates/tracedecay-code-extraction/BUILD.bazel b/crates/tracedecay-code-extraction/BUILD.bazel index af95b4e37a..2686e53cc6 100644 --- a/crates/tracedecay-code-extraction/BUILD.bazel +++ b/crates/tracedecay-code-extraction/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -122,7 +122,7 @@ rust_binary( rust_test( name = "extract_alloc", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/extract_alloc.rs", edition = crate_edition(), version = "0.1.0", @@ -152,7 +152,7 @@ rust_test( rust_test( name = "retained_parse_heap", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/retained_parse_heap.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-code-index-retention/BUILD.bazel b/crates/tracedecay-code-index-retention/BUILD.bazel index e4b868121c..ba0ec6bbda 100644 --- a/crates/tracedecay-code-index-retention/BUILD.bazel +++ b/crates/tracedecay-code-index-retention/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-code-index-runtime/BUILD.bazel b/crates/tracedecay-code-index-runtime/BUILD.bazel index 0aeb58c380..16b31c2d1d 100644 --- a/crates/tracedecay-code-index-runtime/BUILD.bazel +++ b/crates/tracedecay-code-index-runtime/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-code-index/BUILD.bazel b/crates/tracedecay-code-index/BUILD.bazel index d8b8a82152..6b53b9d04d 100644 --- a/crates/tracedecay-code-index/BUILD.bazel +++ b/crates/tracedecay-code-index/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -144,7 +144,7 @@ rust_test( rust_test( name = "resident_accounting", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/resident_accounting.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-configuration/BUILD.bazel b/crates/tracedecay-configuration/BUILD.bazel index e1a4f2e9da..081af45c72 100644 --- a/crates/tracedecay-configuration/BUILD.bazel +++ b/crates/tracedecay-configuration/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-contracts/BUILD.bazel b/crates/tracedecay-contracts/BUILD.bazel index af5ad57458..bfe8b3921e 100644 --- a/crates/tracedecay-contracts/BUILD.bazel +++ b/crates/tracedecay-contracts/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-daemon-control/BUILD.bazel b/crates/tracedecay-daemon-control/BUILD.bazel index 241e7e23a0..e443380f6a 100644 --- a/crates/tracedecay-daemon-control/BUILD.bazel +++ b/crates/tracedecay-daemon-control/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-daemon-identity/BUILD.bazel b/crates/tracedecay-daemon-identity/BUILD.bazel index 081e9f5c16..214ef9963f 100644 --- a/crates/tracedecay-daemon-identity/BUILD.bazel +++ b/crates/tracedecay-daemon-identity/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-daemon-protocol/BUILD.bazel b/crates/tracedecay-daemon-protocol/BUILD.bazel index a559f4d133..0ca78c8491 100644 --- a/crates/tracedecay-daemon-protocol/BUILD.bazel +++ b/crates/tracedecay-daemon-protocol/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-daemon-service/BUILD.bazel b/crates/tracedecay-daemon-service/BUILD.bazel index 62c0ca14e4..ca318b7f52 100644 --- a/crates/tracedecay-daemon-service/BUILD.bazel +++ b/crates/tracedecay-daemon-service/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -78,7 +78,7 @@ rust_library( rust_test( name = "cli_dispatch_schema_bodies", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/cli_dispatch_schema_bodies.rs", edition = crate_edition(), version = "0.1.0", @@ -93,7 +93,7 @@ rust_test( rust_test( name = "shutdown_coordination", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/shutdown_coordination.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-dashboard-api/BUILD.bazel b/crates/tracedecay-dashboard-api/BUILD.bazel index c8fcc9ad7f..a9501693ed 100644 --- a/crates/tracedecay-dashboard-api/BUILD.bazel +++ b/crates/tracedecay-dashboard-api/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-domain/BUILD.bazel b/crates/tracedecay-domain/BUILD.bazel index 99d4e34f7c..9a01d96a6c 100644 --- a/crates/tracedecay-domain/BUILD.bazel +++ b/crates/tracedecay-domain/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -44,7 +44,7 @@ rust_library( rust_test( name = "canonical_scratch_release", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/canonical_scratch_release.rs", edition = crate_edition(), version = "0.1.0", @@ -74,7 +74,7 @@ rust_test( rust_test( name = "session_contract", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_contract.rs", edition = crate_edition(), version = "0.1.0", @@ -114,8 +114,20 @@ filegroup( visibility = ["//visibility:public"], ) +filegroup( + name = "tests_domain_suite_work_product_contract_sources", + srcs = glob(["tests/domain_suite/work_product_contract/**/*.rs"]), + visibility = ["//visibility:public"], +) + filegroup( name = "tests_fixtures", srcs = glob(["tests/fixtures/**"]), visibility = ["//visibility:public"], ) + +filegroup( + name = "tests_sources", + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-framing/BUILD.bazel b/crates/tracedecay-framing/BUILD.bazel index b4afeee2c5..194cff778d 100644 --- a/crates/tracedecay-framing/BUILD.bazel +++ b/crates/tracedecay-framing/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-global-db/BUILD.bazel b/crates/tracedecay-global-db/BUILD.bazel index e8c2a40126..d037760f2b 100644 --- a/crates/tracedecay-global-db/BUILD.bazel +++ b/crates/tracedecay-global-db/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -95,7 +95,7 @@ rust_library( rust_test( name = "registered_schema_fail_closed", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/registered_schema_fail_closed.rs", edition = crate_edition(), version = "0.1.0", @@ -110,7 +110,7 @@ rust_test( rust_test( name = "session_relation_graph", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_relation_graph.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-graph-db/BUILD.bazel b/crates/tracedecay-graph-db/BUILD.bazel index 84fc32b408..00b806622d 100644 --- a/crates/tracedecay-graph-db/BUILD.bazel +++ b/crates/tracedecay-graph-db/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -61,7 +61,7 @@ rust_library( rust_test( name = "at_rest_compact_mutation_probe", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/at_rest_compact_mutation_probe.rs", edition = crate_edition(), version = "0.1.0", @@ -76,7 +76,7 @@ rust_test( rust_test( name = "at_rest_grafeo_probe", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/at_rest_grafeo_probe.rs", edition = crate_edition(), version = "0.1.0", @@ -91,7 +91,7 @@ rust_test( rust_test( name = "at_rest_snapshot", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/at_rest_snapshot.rs", edition = crate_edition(), version = "0.1.0", @@ -106,7 +106,7 @@ rust_test( rust_test( name = "generation_cycle_rss", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/generation_cycle_rss.rs", edition = crate_edition(), version = "0.1.0", @@ -136,7 +136,7 @@ rust_test( rust_test( name = "replay_hydrate_probe", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/replay_hydrate_probe.rs", edition = crate_edition(), version = "0.1.0", @@ -151,7 +151,7 @@ rust_test( rust_test( name = "tiered_storage_rss", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/tiered_storage_rss.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-graph-query/BUILD.bazel b/crates/tracedecay-graph-query/BUILD.bazel index 01a4dff1b1..f96e871b8c 100644 --- a/crates/tracedecay-graph-query/BUILD.bazel +++ b/crates/tracedecay-graph-query/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-hooks/BUILD.bazel b/crates/tracedecay-hooks/BUILD.bazel index 4d2728d179..78886b4b43 100644 --- a/crates/tracedecay-hooks/BUILD.bazel +++ b/crates/tracedecay-hooks/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -44,7 +44,7 @@ rust_library( rust_test( name = "envelope_identity_allocations", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/envelope_identity_allocations.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-host-admission/BUILD.bazel b/crates/tracedecay-host-admission/BUILD.bazel index 74a9016a6b..f5acf584be 100644 --- a/crates/tracedecay-host-admission/BUILD.bazel +++ b/crates/tracedecay-host-admission/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -95,7 +95,7 @@ rust_library( rust_test( name = "host_capture_background_cpu_admission", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/host_capture_background_cpu_admission.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-host-integration/BUILD.bazel b/crates/tracedecay-host-integration/BUILD.bazel index 47d58361dd..fb8bb1fc53 100644 --- a/crates/tracedecay-host-integration/BUILD.bazel +++ b/crates/tracedecay-host-integration/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-lcm/BUILD.bazel b/crates/tracedecay-lcm/BUILD.bazel index c568914227..26963a865b 100644 --- a/crates/tracedecay-lcm/BUILD.bazel +++ b/crates/tracedecay-lcm/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-lsp/BUILD.bazel b/crates/tracedecay-lsp/BUILD.bazel index 735f9451e9..80c12b114b 100644 --- a/crates/tracedecay-lsp/BUILD.bazel +++ b/crates/tracedecay-lsp/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-maintenance/BUILD.bazel b/crates/tracedecay-maintenance/BUILD.bazel index faa7e29260..970b4dca5e 100644 --- a/crates/tracedecay-maintenance/BUILD.bazel +++ b/crates/tracedecay-maintenance/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-mcp-catalog/BUILD.bazel b/crates/tracedecay-mcp-catalog/BUILD.bazel index 60c34b3616..a9c185e56a 100644 --- a/crates/tracedecay-mcp-catalog/BUILD.bazel +++ b/crates/tracedecay-mcp-catalog/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-mcp/BUILD.bazel b/crates/tracedecay-mcp/BUILD.bazel index 7fe0bb72dc..124afe31f9 100644 --- a/crates/tracedecay-mcp/BUILD.bazel +++ b/crates/tracedecay-mcp/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -61,7 +61,7 @@ rust_library( rust_test( name = "broker_stream_transport", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/broker_stream_transport.rs", edition = crate_edition(), version = "0.1.0", @@ -76,7 +76,7 @@ rust_test( rust_test( name = "server_lifecycle", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/server_lifecycle.rs", edition = crate_edition(), version = "0.1.0", @@ -91,7 +91,7 @@ rust_test( rust_test( name = "server_protocol", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/server_protocol.rs", edition = crate_edition(), version = "0.1.0", @@ -106,7 +106,7 @@ rust_test( rust_test( name = "server_read_coalescing", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/server_read_coalescing.rs", edition = crate_edition(), version = "0.1.0", @@ -121,7 +121,7 @@ rust_test( rust_test( name = "server_rmcp", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/server_rmcp.rs", edition = crate_edition(), version = "0.1.0", @@ -136,7 +136,7 @@ rust_test( rust_test( name = "server_settlement", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/server_settlement.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-policy/BUILD.bazel b/crates/tracedecay-policy/BUILD.bazel index 4123f1252c..b44c42af12 100644 --- a/crates/tracedecay-policy/BUILD.bazel +++ b/crates/tracedecay-policy/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-privacy/BUILD.bazel b/crates/tracedecay-privacy/BUILD.bazel index 2e2e35eedc..8de389d2f3 100644 --- a/crates/tracedecay-privacy/BUILD.bazel +++ b/crates/tracedecay-privacy/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -44,7 +44,7 @@ rust_library( rust_test( name = "search_cache_retention", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/search_cache_retention.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-private-fs/BUILD.bazel b/crates/tracedecay-private-fs/BUILD.bazel index 7b824d1db8..be3b4e19d4 100644 --- a/crates/tracedecay-private-fs/BUILD.bazel +++ b/crates/tracedecay-private-fs/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -44,7 +44,7 @@ rust_library( rust_test( name = "atomic_publication", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/atomic_publication.rs", edition = crate_edition(), version = "0.1.0", @@ -60,7 +60,7 @@ rust_test( rust_test( name = "framed_log", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/framed_log.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-project/BUILD.bazel b/crates/tracedecay-project/BUILD.bazel index e56a7710ae..219868b08e 100644 --- a/crates/tracedecay-project/BUILD.bazel +++ b/crates/tracedecay-project/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-query/BUILD.bazel b/crates/tracedecay-query/BUILD.bazel index 534e7e5a73..c44e061194 100644 --- a/crates/tracedecay-query/BUILD.bazel +++ b/crates/tracedecay-query/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -191,7 +191,7 @@ rust_binary( rust_test( name = "canonical_execution_equivalence", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/canonical_execution_equivalence.rs", edition = crate_edition(), version = "0.1.0", @@ -208,7 +208,7 @@ rust_test( rust_test( name = "index_bench_workload", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/index_bench_workload.rs", edition = crate_edition(), version = "0.1.0", @@ -225,7 +225,7 @@ rust_test( rust_test( name = "retrieval_contract_spine", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/retrieval_contract_spine.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-runtime-core/BUILD.bazel b/crates/tracedecay-runtime-core/BUILD.bazel index 8da14bc252..1dd5a08386 100644 --- a/crates/tracedecay-runtime-core/BUILD.bazel +++ b/crates/tracedecay-runtime-core/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -61,7 +61,7 @@ rust_library( rust_test( name = "git_discovery_authority", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/git_discovery_authority.rs", edition = crate_edition(), version = "0.1.0", @@ -76,7 +76,7 @@ rust_test( rust_test( name = "git_repository_authority", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/git_repository_authority.rs", edition = crate_edition(), version = "0.1.0", @@ -106,7 +106,7 @@ rust_test( rust_test( name = "sqlite_foreign_snapshot_cost", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/sqlite_foreign_snapshot_cost.rs", edition = crate_edition(), version = "0.1.0", @@ -121,7 +121,7 @@ rust_test( rust_test( name = "tracing_capture_keepalive", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/tracing_capture_keepalive.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-rusqlite-runtime/BUILD.bazel b/crates/tracedecay-rusqlite-runtime/BUILD.bazel index afacce46b2..1247a21353 100644 --- a/crates/tracedecay-rusqlite-runtime/BUILD.bazel +++ b/crates/tracedecay-rusqlite-runtime/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -44,7 +44,7 @@ rust_library( rust_test( name = "rusqlite_suite", - srcs = glob(["tests/rusqlite_suite/**/*.rs"]) + glob(["tests/support/**/*.rs"]) + ["//tests:storage_runtime_rusqlite_suite"], + srcs = glob(["tests/rusqlite_suite/**/*.rs"]) + glob(["tests/support/**/*.rs"]) + ["//tests:storage_runtime_rusqlite_suite_sources"], crate_root = "tests/rusqlite_suite/main.rs", edition = crate_edition(), version = "0.1.0", @@ -59,7 +59,7 @@ rust_test( rust_test( name = "work_product_read_scaling", - srcs = glob(["tests/**/*.rs"]) + ["//tests:storage_runtime_rusqlite_suite"], + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + ["//tests:storage_runtime_rusqlite_suite_sources"], crate_root = "tests/work_product_read_scaling.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-sdk/BUILD.bazel b/crates/tracedecay-sdk/BUILD.bazel index 4fe803b47e..a3508777e1 100644 --- a/crates/tracedecay-sdk/BUILD.bazel +++ b/crates/tracedecay-sdk/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -44,7 +44,7 @@ rust_library( rust_test( name = "remote_client_proxy", - srcs = glob(["tests/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + ["//tests:support_sources"], crate_root = "tests/remote_client_proxy.rs", edition = crate_edition(), version = "0.1.0", @@ -60,7 +60,7 @@ rust_test( rust_test( name = "sdk_suite", - srcs = glob(["tests/sdk_suite/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/sdk_suite/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/sdk_suite/main.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-search-eval/BUILD.bazel b/crates/tracedecay-search-eval/BUILD.bazel index 5b1412e47d..8afb9c7258 100644 --- a/crates/tracedecay-search-eval/BUILD.bazel +++ b/crates/tracedecay-search-eval/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-session-memory/BUILD.bazel b/crates/tracedecay-session-memory/BUILD.bazel index 4fb7b0fb91..23a4845719 100644 --- a/crates/tracedecay-session-memory/BUILD.bazel +++ b/crates/tracedecay-session-memory/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-session-runtime/BUILD.bazel b/crates/tracedecay-session-runtime/BUILD.bazel index 7958e3b5da..c3a38fb0af 100644 --- a/crates/tracedecay-session-runtime/BUILD.bazel +++ b/crates/tracedecay-session-runtime/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -78,7 +78,7 @@ rust_library( rust_test( name = "session_backlog_read_cost", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_backlog_read_cost.rs", edition = crate_edition(), version = "0.1.0", @@ -93,7 +93,7 @@ rust_test( rust_test( name = "session_drain_read_cost", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_drain_read_cost.rs", edition = crate_edition(), version = "0.1.0", @@ -108,7 +108,7 @@ rust_test( rust_test( name = "session_idle_history_commits", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_idle_history_commits.rs", edition = crate_edition(), version = "0.1.0", @@ -123,7 +123,7 @@ rust_test( rust_test( name = "session_ingest_commit_cost", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_ingest_commit_cost.rs", edition = crate_edition(), version = "0.1.0", @@ -138,7 +138,7 @@ rust_test( rust_test( name = "session_ingest_index_read_cost", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_ingest_index_read_cost.rs", edition = crate_edition(), version = "0.1.0", @@ -153,7 +153,7 @@ rust_test( rust_test( name = "session_live_ingest_work", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_live_ingest_work.rs", edition = crate_edition(), version = "0.1.0", @@ -168,7 +168,7 @@ rust_test( rust_test( name = "session_refresh_read_cost", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_refresh_read_cost.rs", edition = crate_edition(), version = "0.1.0", @@ -183,7 +183,7 @@ rust_test( rust_test( name = "session_retention_read_cost", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_retention_read_cost.rs", edition = crate_edition(), version = "0.1.0", @@ -198,7 +198,7 @@ rust_test( rust_test( name = "session_retention_write_cost", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_retention_write_cost.rs", edition = crate_edition(), version = "0.1.0", @@ -213,7 +213,7 @@ rust_test( rust_test( name = "session_wal_streaming", - srcs = glob(["tests/**/*.rs"]), + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]), crate_root = "tests/session_wal_streaming.rs", edition = crate_edition(), version = "0.1.0", diff --git a/crates/tracedecay-session-temporal-store/BUILD.bazel b/crates/tracedecay-session-temporal-store/BUILD.bazel index 33929ba1c2..b78c6005d1 100644 --- a/crates/tracedecay-session-temporal-store/BUILD.bazel +++ b/crates/tracedecay-session-temporal-store/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-sessions/BUILD.bazel b/crates/tracedecay-sessions/BUILD.bazel index 79a3fcfefe..c97c015552 100644 --- a/crates/tracedecay-sessions/BUILD.bazel +++ b/crates/tracedecay-sessions/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-source-edit/BUILD.bazel b/crates/tracedecay-source-edit/BUILD.bazel index 9fae365ead..53ea2ef374 100644 --- a/crates/tracedecay-source-edit/BUILD.bazel +++ b/crates/tracedecay-source-edit/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-store-runtime/BUILD.bazel b/crates/tracedecay-store-runtime/BUILD.bazel index 8fb9940478..69e879ec25 100644 --- a/crates/tracedecay-store-runtime/BUILD.bazel +++ b/crates/tracedecay-store-runtime/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-store/BUILD.bazel b/crates/tracedecay-store/BUILD.bazel index 95674f0367..0dc65a5276 100644 --- a/crates/tracedecay-store/BUILD.bazel +++ b/crates/tracedecay-store/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-temporal-query/BUILD.bazel b/crates/tracedecay-temporal-query/BUILD.bazel index aafe9a1776..a5dbddd276 100644 --- a/crates/tracedecay-temporal-query/BUILD.bazel +++ b/crates/tracedecay-temporal-query/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay-tool-catalog/BUILD.bazel b/crates/tracedecay-tool-catalog/BUILD.bazel index b6b81a25ba..10f53b9c6e 100644 --- a/crates/tracedecay-tool-catalog/BUILD.bazel +++ b/crates/tracedecay-tool-catalog/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) diff --git a/crates/tracedecay/BUILD.bazel b/crates/tracedecay/BUILD.bazel index 183b2f0a97..e752f261f6 100644 --- a/crates/tracedecay/BUILD.bazel +++ b/crates/tracedecay/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py package(default_visibility = ["//visibility:public"]) @@ -10,7 +10,7 @@ load("//scripts:bazel/rust_test.bzl", "perf_rustc_flags", "rust_test") rust_library( name = "tracedecay", - srcs = glob(["src/**/*.rs"]) + glob(["benches/rmcp/**/*.rs", "benches/session_temporal/**/*.rs", "tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["src/**/*.rs"]) + glob(["benches/rmcp/**/*.rs", "benches/session_temporal/**/*.rs", "tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "src/lib.rs", crate_name = "tracedecay", edition = crate_edition(), @@ -27,7 +27,7 @@ rust_library( rust_library( name = "tracedecay__+rmcp-benchmark_+test-helpers_+test-transport__dev", - srcs = glob(["src/**/*.rs"]) + glob(["benches/rmcp/**/*.rs", "benches/session_temporal/**/*.rs", "tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["src/**/*.rs"]) + glob(["benches/rmcp/**/*.rs", "benches/session_temporal/**/*.rs", "tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "src/lib.rs", crate_name = "tracedecay", edition = crate_edition(), @@ -44,7 +44,7 @@ rust_library( rust_library( name = "tracedecay__-default__d2de7b7dcda", - srcs = glob(["src/**/*.rs"]) + glob(["benches/rmcp/**/*.rs", "benches/session_temporal/**/*.rs", "tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["src/**/*.rs"]) + glob(["benches/rmcp/**/*.rs", "benches/session_temporal/**/*.rs", "tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "src/lib.rs", crate_name = "tracedecay", edition = crate_edition(), @@ -61,7 +61,7 @@ rust_library( rust_library( name = "tracedecay__bare__d18d185dadf", - srcs = glob(["src/**/*.rs"]) + glob(["benches/rmcp/**/*.rs", "benches/session_temporal/**/*.rs", "tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["src/**/*.rs"]) + glob(["benches/rmcp/**/*.rs", "benches/session_temporal/**/*.rs", "tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "src/lib.rs", crate_name = "tracedecay", edition = crate_edition(), @@ -92,7 +92,7 @@ rust_binary( rust_test( name = "agent_suite", - srcs = glob(["tests/agent_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/agent_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/agent_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -109,7 +109,7 @@ rust_test( rust_test( name = "automation_runner_test", - srcs = glob(["tests/automation_runner_test/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/automation_runner_test/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/automation_runner_test/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -141,7 +141,7 @@ rust_test( rust_test( name = "cursor_observation_identity_fallback", - srcs = glob(["tests/**/*.rs"]) + ["//crates/tracedecay-domain:tests", "//crates/tracedecay-domain:tests_domain_suite_work_product_contract", "//tests:support"], + srcs = glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + ["//crates/tracedecay-domain:tests_domain_suite_work_product_contract_sources", "//crates/tracedecay-domain:tests_sources", "//tests:support_sources"], crate_root = "tests/cursor_observation_identity_fallback.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -151,14 +151,14 @@ rust_test( rustc_flags = perf_rustc_flags(1), crate_features = ["default", "full", "lite", "production", "rmcp-benchmark", "test-helpers", "test-transport", "token-counting"], compile_data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"] + ["//benchmark_data:claude-observation", "//crates/tracedecay-agent-hosts:src_agents_hermes_templates", "//crates/tracedecay-application:src_advisory_fixtures_provider_branch_review", "//crates/tracedecay-domain:tests_fixtures", "//crates/tracedecay-hooks:fixtures_host_events", "//crates/tracedecay-hooks:fixtures_host_events_claude", "//crates/tracedecay-hooks:fixtures_host_events_codex", "//crates/tracedecay-hooks:fixtures_host_events_cursor", "//crates/tracedecay-hooks:fixtures_host_events_droid", "//crates/tracedecay-hooks:fixtures_host_events_hermes", "//crates/tracedecay-hooks:fixtures_host_events_kimi", "//crates/tracedecay-hooks:fixtures_host_events_opencode", "//crates/tracedecay-hooks:fixtures_host_events_pi", "//crates/tracedecay-session-memory:src", "//plugin:skills_managing-session-context", "//plugin:skills_managing-session-context_references"], - data = glob(["tests/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**"], allow_empty = True) + ["//crates/tracedecay-domain:tests", "//crates/tracedecay-domain:tests_domain_suite_work_product_contract", "//tests:support"] + ["//tests:fixtures"], + data = glob(["tests/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**"], allow_empty = True) + ["//crates/tracedecay-domain:tests_domain_suite_work_product_contract", "//crates/tracedecay-domain:tests", "//tests:support"] + ["//tests:fixtures"], rustc_env = {"CARGO_TARGET_TMPDIR": "/tmp"}, runfiles_env = {"TRACEDECAY_SEARCH_EVAL_DIRECT_TEST_BIN": "//crates/tracedecay-search-eval:tracedecay-search-eval-direct__test", "TRACEDECAY_SEARCH_EVAL_TEST_BIN": "//crates/tracedecay-search-eval:tracedecay-search-eval_bin__test", "TRACEDECAY_TEST_BIN": "//crates/tracedecay-cli:tracedecay__test", "TRACEDECAY_TYPESCRIPT_SDK_DIST": "//sdks/typescript:dist", "TRACEDECAY_TYPESCRIPT_SDK_RUNNER": "//sdks/typescript:typed_terminal_live"}, ) rust_test( name = "daemon_suite", - srcs = glob(["tests/daemon_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/daemon_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/daemon_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -175,7 +175,7 @@ rust_test( rust_test( name = "dashboard_api_test", - srcs = glob(["tests/dashboard_api_test/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/dashboard_api_test/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/dashboard_api_test/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -192,7 +192,7 @@ rust_test( rust_test( name = "graph_suite", - srcs = glob(["tests/graph_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/graph_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/graph_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -209,7 +209,7 @@ rust_test( rust_test( name = "hermes_suite", - srcs = glob(["tests/hermes_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/hermes_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/hermes_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -226,7 +226,7 @@ rust_test( rust_test( name = "hooks_lsp_suite", - srcs = glob(["tests/hooks_lsp_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/hooks_lsp_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/hooks_lsp_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -244,7 +244,7 @@ rust_test( rust_test( name = "mcp_suite", size = "large", - srcs = glob(["tests/mcp_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/mcp_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/mcp_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -261,7 +261,7 @@ rust_test( rust_test( name = "memory_suite", - srcs = glob(["tests/memory_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/memory_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/memory_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -294,7 +294,7 @@ rust_test( rust_test( name = "runtime_acceptance_suite", - srcs = glob(["tests/runtime_acceptance_suite/**/*.rs"]) + glob(["tests/common/**/*.rs", "tests/session_suite/**/*.rs", "tests/session_suite/temporal_projection/**/*.rs", "tests/storage_suite/**/*.rs"]) + ["//crates/tracedecay-domain:tests", "//crates/tracedecay-domain:tests_domain_suite_work_product_contract", "//tests:support"], + srcs = glob(["tests/runtime_acceptance_suite/**/*.rs"]) + glob(["tests/common/**/*.rs", "tests/session_suite/**/*.rs", "tests/session_suite/temporal_projection/**/*.rs", "tests/storage_suite/**/*.rs"]) + ["//crates/tracedecay-domain:tests_domain_suite_work_product_contract_sources", "//crates/tracedecay-domain:tests_sources", "//tests:support_sources"], crate_root = "tests/runtime_acceptance_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -304,14 +304,14 @@ rust_test( rustc_flags = perf_rustc_flags(1), crate_features = ["default", "full", "lite", "production", "rmcp-benchmark", "test-helpers", "test-transport", "token-counting"], compile_data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"] + ["//crates/tracedecay-agent-hosts:src_agents_hermes_templates", "//crates/tracedecay-application:src_advisory_fixtures_provider_branch_review", "//crates/tracedecay-domain:tests_fixtures", "//crates/tracedecay-hooks:fixtures_host_events_claude", "//crates/tracedecay-hooks:fixtures_host_events_codex", "//crates/tracedecay-hooks:fixtures_host_events_cursor"], - data = glob(["tests/runtime_acceptance_suite/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//crates/tracedecay-domain:tests", "//crates/tracedecay-domain:tests_domain_suite_work_product_contract", "//tests:support"] + ["//tests:fixtures"], + data = glob(["tests/runtime_acceptance_suite/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//crates/tracedecay-domain:tests_domain_suite_work_product_contract", "//crates/tracedecay-domain:tests", "//tests:support"] + ["//tests:fixtures"], rustc_env = {"CARGO_TARGET_TMPDIR": "/tmp"}, runfiles_env = {"TRACEDECAY_SEARCH_EVAL_TEST_BIN": "//crates/tracedecay-search-eval:tracedecay-search-eval_bin__test", "TRACEDECAY_TEST_BIN": "//crates/tracedecay-cli:tracedecay__test"}, ) rust_test( name = "session_suite", - srcs = glob(["tests/session_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/session_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/session_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -328,7 +328,7 @@ rust_test( rust_test( name = "storage_runtime_rusqlite_suite", - srcs = ["//tests:storage_runtime_rusqlite_suite"], + srcs = ["//tests:storage_runtime_rusqlite_suite_sources"], crate_root = "//tests:storage_runtime_rusqlite_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -343,7 +343,7 @@ rust_test( rust_test( name = "storage_suite", - srcs = glob(["tests/storage_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/storage_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/storage_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -360,7 +360,7 @@ rust_test( rust_test( name = "transcript_ingest_suite", - srcs = glob(["tests/transcript_ingest_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/transcript_ingest_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/transcript_ingest_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -377,7 +377,7 @@ rust_test( rust_test( name = "transport_acceptance_suite", - srcs = glob(["tests/transport_acceptance_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support"], + srcs = glob(["tests/transport_acceptance_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"], crate_root = "tests/transport_acceptance_suite/main.rs", edition = crate_edition(), version = "1.0.0-beta.76", @@ -490,3 +490,9 @@ filegroup( srcs = glob(["tests/common/**"]), visibility = ["//visibility:public"], ) + +filegroup( + name = "tests_common_sources", + srcs = glob(["tests/common/**/*.rs"]), + visibility = ["//visibility:public"], +) diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel index 8f85a21177..93ab68dfb3 100644 --- a/scripts/BUILD.bazel +++ b/scripts/BUILD.bazel @@ -3,6 +3,7 @@ load("//scripts:bazel/rust_test.bzl", "rust_test") exports_files( [ "bazel/rust_test.bzl", + "bazel/gen_builds.py", "bazel/rust_test_environment_probe.txt", "check-dashboard-bundle.py", ], diff --git a/scripts/bazel/gen_builds.py b/scripts/bazel/gen_builds.py old mode 100644 new mode 100755 index 3dc05179c8..124c68bbc5 --- a/scripts/bazel/gen_builds.py +++ b/scripts/bazel/gen_builds.py @@ -42,7 +42,7 @@ BANNER = ( "# Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock.\n" - "# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py\n" + "# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py\n" ) # Generated filegroups embedded in a handwritten BUILD.bazel live inside @@ -663,7 +663,7 @@ def owner_pkg(repo_rel): return str(Path(*parts[:2])), str(Path(*parts[2:-1])) return parts[0], str(Path(*parts[1:-1])) - def group_label(repo_rel): + def group_label(repo_rel, *, sources=False): """filegroup label + pkg-relative file label for an out-of-package file, registering the group for emission in the owning package.""" pkg_dir, group_rel = owner_pkg(repo_rel) @@ -673,6 +673,12 @@ def group_label(repo_rel): gname = "shared_fixtures" entry = out_of_pkg.setdefault((pkg_dir, gname), {"files": set(), "glob": group_rel}) entry["files"].add(pkg_rel) + if sources: + gname += "_sources" + entry = out_of_pkg.setdefault( + (pkg_dir, gname), {"files": set(), "glob": group_rel, "sources": True} + ) + entry["files"].add(pkg_rel) return f"//{pkg_dir}:{gname}", pkg_dir, pkg_rel def srcs_for(p, t): @@ -682,10 +688,13 @@ def srcs_for(p, t): rel = rel_src(p, t) if rel.startswith(".."): repo_rel = Path(t["src_path"]).resolve().relative_to(REPO) - glabel, pkg_dir, pkg_rel = group_label(repo_rel) + glabel, pkg_dir, pkg_rel = group_label(repo_rel, sources=True) return (f'["{glabel}"]', f'"//{pkg_dir}:{pkg_rel}"') d = Path(rel).parent - return (f'glob(["{d}/**/*.rs"])', f'"{rel}"') + # Package-root fixtures are data, including deliberately invalid or + # vendored Rust. Nested support/fixtures modules remain source inputs. + exclude = ', exclude = ["tests/fixtures/**"]' if d == Path("tests") else "" + return (f'glob(["{d}/**/*.rs"]{exclude})', f'"{rel}"') def extra_mod_srcs(p, t, covered=()): """(pkg_relative_srcs, filegroup_labels) for `#[path = "..."]` modules @@ -722,7 +731,7 @@ def extra_mod_srcs(p, t, covered=()): + "/**/*.rs" ) continue - glabel, _, _ = group_label(repo_rel) + glabel, _, _ = group_label(repo_rel, sources=True) labels.add(glabel) return sorted(local), sorted(labels), sorted(pulled_dirs) @@ -1074,9 +1083,10 @@ def unique_name(want): f"{Path(rel_src(p, t)).parent}/**", *[f"{directory}/**" for directory in RESOURCE_DIRS if directory != "src"], ]) - data = srcs if srcs.startswith("[") else ( + # Source groups keep separate full resource groups for runfiles. + data = srcs.replace("_sources\"", "\"") if srcs.startswith("[") else ( f"glob([{q(runtime_globs)}], allow_empty = True)" - + (" + [" + q(mod_labels) + "]" if mod_labels else "") + + (" + [" + q([label.removesuffix("_sources") for label in mod_labels]) + "]" if mod_labels else "") ) # Suites read the shared fixtures as ../../tests/fixtures from the # crate directory, where the launcher starts them. @@ -1198,13 +1208,19 @@ def package_rules(pkg_dir, exports): name member crate roots, so they need none.""" rules = [] for gname, entry in sorted(by_pkg.get(pkg_dir, {}).items()): + pattern = f'{entry["glob"]}/**' + exclude = "" + if entry.get("sources"): + pattern += "/*.rs" + if entry["glob"] == "tests": + exclude = ', exclude = ["tests/fixtures/**"]' rules.append( f'filegroup(\n name = "{gname}",\n' - f' srcs = glob(["{entry["glob"]}/**"]),\n' + f' srcs = glob(["{pattern}"]{exclude}),\n' ' visibility = ["//visibility:public"],\n)' ) crate_roots = sorted(entry["files"]) - if exports and crate_roots: + if exports and crate_roots and not entry.get("sources"): rules.append( "exports_files([" + q(crate_roots) + "],\n" ' visibility = ["//visibility:public"],\n)' @@ -1340,7 +1356,7 @@ def render_shared(path, pkg_dir): print("generated BUILD.bazel files are out of date:") for path in stale: print(f" {path.relative_to(REPO)}") - print("rerun: python3 scripts/bazel/gen_builds.py") + print("rerun: bazel run //scripts:bazel/gen_builds.py") # Show the first diffs so CI output explains the divergence. import difflib shown = 0 diff --git a/scripts/test-neutralize-release-pr-closing-keywords.py b/scripts/test-neutralize-release-pr-closing-keywords.py index 1d8691b785..9f827ce98b 100644 --- a/scripts/test-neutralize-release-pr-closing-keywords.py +++ b/scripts/test-neutralize-release-pr-closing-keywords.py @@ -229,6 +229,12 @@ def git(*args: str, cwd: Path = main) -> None: edited_path = scratch_path / "edited.md" stubs = { "cargo": cargo_stub.format(lockfile=checkout / "Cargo.lock"), + "bazel": ( + "#!/bin/sh\n" + '[ "$#" -eq 2 ] && [ "$1" = run ] && ' + '[ "$2" = //scripts:bazel/gen_builds.py ] || exit 64\n' + 'exec python3 scripts/bazel/gen_builds.py\n' + ), "gh": ( "#!/bin/sh\n" 'case "$1 $2" in\n' diff --git a/scripts/update-release-pr-lockfile.sh b/scripts/update-release-pr-lockfile.sh index 5cc8474415..c1363d7c50 100755 --- a/scripts/update-release-pr-lockfile.sh +++ b/scripts/update-release-pr-lockfile.sh @@ -33,7 +33,7 @@ repository_root=$PWD # pnpm validates workspace crate versions even for a frozen npm installation. # Repair the release's Cargo lock before installing generated-build inputs. pnpm install --frozen-lockfile -python3 scripts/bazel/gen_builds.py +bazel run //scripts:bazel/gen_builds.py pnpm --dir sdks/typescript run build pnpm --dir plugin/chatgpt-extension run build diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index aa23c045dd..861baa535c 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -1,5 +1,5 @@ # Generated by scripts/bazel/gen_builds.py from Cargo.toml + Cargo.lock. -# Regenerate instead of editing. python3 scripts/bazel/gen_builds.py +# Regenerate instead of editing. bazel run //scripts:bazel/gen_builds.py # BEGIN generated by scripts/bazel/gen_builds.py filegroup( @@ -12,6 +12,12 @@ exports_files(["storage_runtime_rusqlite_suite/main.rs", "storage_runtime_rusqli visibility = ["//visibility:public"], ) +filegroup( + name = "storage_runtime_rusqlite_suite_sources", + srcs = glob(["storage_runtime_rusqlite_suite/**/*.rs"]), + visibility = ["//visibility:public"], +) + filegroup( name = "support", srcs = glob(["support/**"]), @@ -22,6 +28,12 @@ exports_files(["support/isolated_profile.rs"], visibility = ["//visibility:public"], ) +filegroup( + name = "support_sources", + srcs = glob(["support/**/*.rs"]), + visibility = ["//visibility:public"], +) + filegroup( name = "fixtures", srcs = glob(["fixtures/**"]), From 60666e363ff8dc60761b3444f4d0b31b41de7b97 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 15:35:55 -0700 Subject: [PATCH 17/29] fix(bazel): use native CLI outputs in developer harnesses --- scripts/bench-hot-paths.py | 3 +- scripts/ci-pr-dogfood-smoke.sh | 2 +- scripts/claude_stock_integration.sh | 4 +- scripts/efficiency-scorecard.py | 31 ++++++------- scripts/hermes_plugin_unit_check.py | 5 ++- scripts/hermes_stock_integration.sh | 4 +- scripts/lib/stock-host.sh | 8 ++-- scripts/mcp-conformance-smoke.sh | 27 +++--------- scripts/mcp_probe/README.md | 4 +- scripts/mcp_probe/probe.py | 4 +- scripts/opencode_stock_integration.sh | 4 +- scripts/perf-gate.sh | 18 +++----- scripts/require-exact-test.sh | 9 ++-- scripts/test-efficiency-scorecard.py | 51 ++++++++++++++++++++++ scripts/tool-sweep.sh | 2 +- scripts/with-isolated-tracedecay-daemon.sh | 44 +++++-------------- 16 files changed, 116 insertions(+), 104 deletions(-) diff --git a/scripts/bench-hot-paths.py b/scripts/bench-hot-paths.py index a9879f2a6b..decb758652 100755 --- a/scripts/bench-hot-paths.py +++ b/scripts/bench-hot-paths.py @@ -27,7 +27,8 @@ Linux instead of reported as zero. Usage: - scripts/bench-hot-paths.py --bin target/release/tracedecay + bazel build --config=release //crates/tracedecay-cli:tracedecay + scripts/bench-hot-paths.py --bin bazel-bin/crates/tracedecay-cli/tracedecay scripts/bench-hot-paths.py --bin ./tracedecay --samples 40 --out target/bench-hot-paths/run1 Exit status: 0 when the run completed (whatever the numbers), 2 on a harness diff --git a/scripts/ci-pr-dogfood-smoke.sh b/scripts/ci-pr-dogfood-smoke.sh index 77657b73a7..5c016e9de7 100755 --- a/scripts/ci-pr-dogfood-smoke.sh +++ b/scripts/ci-pr-dogfood-smoke.sh @@ -519,7 +519,7 @@ run_smoke() { main() { local binary project_root base_ref head_ref base_branch head_branch checked_out_oid head_oid started_ms status - binary="${TRACEDECAY_BIN:-$REPO_ROOT/target/debug/tracedecay}" + binary="${TRACEDECAY_BIN:-$REPO_ROOT/bazel-bin/crates/tracedecay-cli/tracedecay}" project_root="${TRACEDECAY_DOGFOOD_PROJECT:-$REPO_ROOT}" base_ref="${TRACEDECAY_DOGFOOD_BASE_REF:-}" head_ref="${TRACEDECAY_DOGFOOD_HEAD_REF:-HEAD}" diff --git a/scripts/claude_stock_integration.sh b/scripts/claude_stock_integration.sh index 7e91266b09..55bdce368d 100755 --- a/scripts/claude_stock_integration.sh +++ b/scripts/claude_stock_integration.sh @@ -7,11 +7,11 @@ # `claude-integration` CI job and runnable locally: # # npm install --global @anthropic-ai/claude-code@ -# cargo build -p tracedecay-cli --bin tracedecay +# bazel build //crates/tracedecay-cli:tracedecay # scripts/claude_stock_integration.sh # # Environment: -# TRACEDECAY_BIN tracedecay binary to install/test (default: target/debug/tracedecay) +# TRACEDECAY_BIN tracedecay binary to install/test (default: bazel-bin/crates/tracedecay-cli/tracedecay) # CLAUDE_BIN stock claude binary (default: claude on PATH) # # Everything runs in a throwaway HOME and a throwaway project. No model calls diff --git a/scripts/efficiency-scorecard.py b/scripts/efficiency-scorecard.py index 681eba44aa..b6cd1d9e40 100755 --- a/scripts/efficiency-scorecard.py +++ b/scripts/efficiency-scorecard.py @@ -7,7 +7,7 @@ diffing two files. scripts/efficiency-scorecard.py # build + run (3 runs) - scripts/efficiency-scorecard.py --binary target/release/tracedecay + scripts/efficiency-scorecard.py --binary /path/to/tracedecay scripts/efficiency-scorecard.py --quick # 1 run smoke (~1 min) scripts/efficiency-scorecard.py --label b9181367ba \ --binary /path/to/old/tracedecay --output target/effscore-base @@ -1205,28 +1205,29 @@ def flatten_store_bytes(run: dict) -> None: def build_binary() -> Path: - jobs = os.environ.get("CARGO_BUILD_JOBS", "8") - print(f"scorecard: building tracedecay (release, jobs={jobs})", file=sys.stderr) - env = dict(os.environ, CARGO_BUILD_JOBS=jobs, TRACEDECAY_SKIP_DASHBOARD_BUILD="1") + print("scorecard: building tracedecay (Bazel release)", file=sys.stderr) completed = subprocess.run( ( - "cargo", + "bazel", "build", - "--locked", - "--release", - "-p", - "tracedecay-cli", - "--bin", - "tracedecay", + "--config=release", + "//crates/tracedecay-cli:tracedecay", ), cwd=REPO_ROOT, - env=env, check=False, ) if completed.returncode != 0: raise HarnessError("the tracedecay release build failed") - target_dir = Path(os.environ.get("CARGO_TARGET_DIR", REPO_ROOT / "target")) - binary = target_dir / "release" / "tracedecay" + output = subprocess.run( + ("bazel", "info", "--config=release", "bazel-bin"), + cwd=REPO_ROOT, + capture_output=True, + text=True, + check=False, + ) + if output.returncode != 0 or not output.stdout.strip(): + raise HarnessError(f"could not resolve the Bazel output directory: {output.stderr.strip()}") + binary = Path(output.stdout.strip()) / "crates" / "tracedecay-cli" / "tracedecay" if not binary.is_file(): raise HarnessError(f"expected a binary at {binary} after the build") return binary @@ -1243,7 +1244,7 @@ def main() -> int: parser = argparse.ArgumentParser( description=__doc__.splitlines()[0], formatter_class=argparse.RawDescriptionHelpFormatter ) - parser.add_argument("--binary", help="prebuilt tracedecay binary (default: cargo build)") + parser.add_argument("--binary", help="prebuilt tracedecay binary (default: Bazel release build)") parser.add_argument( "--output", default=str(REPO_ROOT / "target" / "efficiency-scorecard"), diff --git a/scripts/hermes_plugin_unit_check.py b/scripts/hermes_plugin_unit_check.py index 71c07d0bf5..45c5d6e4f2 100644 --- a/scripts/hermes_plugin_unit_check.py +++ b/scripts/hermes_plugin_unit_check.py @@ -26,7 +26,8 @@ python3 scripts/hermes_plugin_unit_check.py [plugin_dir] plugin_dir defaults to a fresh install generated into a temp HOME via the -tracedecay binary named by $TRACEDECAY_BIN (default: target/debug/tracedecay). +tracedecay binary named by $TRACEDECAY_BIN +(default: bazel-bin/crates/tracedecay-cli/tracedecay). """ import copy @@ -54,7 +55,7 @@ def generate_plugin(work: Path) -> Path: """Generates the plugin into a throwaway HOME using the real installer.""" repo_root = Path(__file__).resolve().parent.parent bin_path = Path( - os.environ.get("TRACEDECAY_BIN", repo_root / "target" / "debug" / "tracedecay") + os.environ.get("TRACEDECAY_BIN", repo_root / "bazel-bin" / "crates" / "tracedecay-cli" / "tracedecay") ) assert bin_path.is_file(), f"tracedecay binary not found at {bin_path}" home = work / "home" diff --git a/scripts/hermes_stock_integration.sh b/scripts/hermes_stock_integration.sh index a39081c4dd..638ae0c736 100755 --- a/scripts/hermes_stock_integration.sh +++ b/scripts/hermes_stock_integration.sh @@ -5,11 +5,11 @@ # # git clone https://github.com/NousResearch/hermes-agent.git /tmp/hermes-upstream # git -C /tmp/hermes-upstream checkout -# cargo build -p tracedecay-cli --bin tracedecay +# bazel build //crates/tracedecay-cli:tracedecay # scripts/hermes_stock_integration.sh # # Environment: -# TRACEDECAY_BIN tracedecay binary to install/test (default: target/debug/tracedecay) +# TRACEDECAY_BIN tracedecay binary to install/test (default: bazel-bin/crates/tracedecay-cli/tracedecay) # HERMES_UPSTREAM_DIR stock hermes-agent checkout (default: /tmp/hermes-upstream) # # Everything is offline and deterministic: a throwaway user HOME (and its diff --git a/scripts/lib/stock-host.sh b/scripts/lib/stock-host.sh index 579c917108..c10bc32771 100644 --- a/scripts/lib/stock-host.sh +++ b/scripts/lib/stock-host.sh @@ -16,15 +16,15 @@ readonly STOCK_HOST_REPO_ROOT STOCK_HOST_TRACEDECAY_BIN="" # Prints the absolutized tracedecay binary under test ($TRACEDECAY_BIN, or the -# default cargo debug output) and requires it to be executable. +# default Bazel CLI output) and requires it to be executable. resolve_tracedecay_bin() { local bin - bin="${TRACEDECAY_BIN:-$STOCK_HOST_REPO_ROOT/target/debug/tracedecay}" - bin="$(cd "$(dirname "$bin")" && pwd)/$(basename "$bin")" + bin="${TRACEDECAY_BIN:-$STOCK_HOST_REPO_ROOT/bazel-bin/crates/tracedecay-cli/tracedecay}" if [[ ! -x "$bin" ]]; then - echo "error: tracedecay binary not found at $bin (build with: cargo build -p tracedecay-cli --bin tracedecay)" >&2 + echo "error: tracedecay binary not found at $bin (build with: bazel build //crates/tracedecay-cli:tracedecay)" >&2 return 1 fi + bin="$(cd "$(dirname "$bin")" && pwd)/$(basename "$bin")" printf '%s\n' "$bin" } diff --git a/scripts/mcp-conformance-smoke.sh b/scripts/mcp-conformance-smoke.sh index 3049df0d68..e4593f3050 100755 --- a/scripts/mcp-conformance-smoke.sh +++ b/scripts/mcp-conformance-smoke.sh @@ -19,8 +19,9 @@ # into the temp dir, so it never touches the user's real tracedecay state. # # Usage: -# scripts/mcp-conformance-smoke.sh # auto-detect binary -# TRACEDECAY_BIN=target/debug/tracedecay scripts/mcp-conformance-smoke.sh +# bazel build //crates/tracedecay-cli:tracedecay +# scripts/mcp-conformance-smoke.sh # use the Bazel output +# TRACEDECAY_BIN=/path/to/tracedecay scripts/mcp-conformance-smoke.sh set -euo pipefail @@ -292,30 +293,12 @@ cleanup() { exit "$status" } -find_tracedecay_bin() { - local target_dir candidate - - if [[ -n "${TRACEDECAY_BIN:-}" ]]; then - printf '%s\n' "$TRACEDECAY_BIN" - return - fi - - target_dir="${CARGO_TARGET_DIR:-$REPO_ROOT/target}" - for candidate in "$target_dir/debug/tracedecay" "$target_dir/release/tracedecay"; do - if [[ -x "$candidate" ]]; then - printf '%s\n' "$candidate" - return - fi - done - command -v tracedecay || true -} - main() { local tracedecay_bin npm_cache fixture status - tracedecay_bin="$(find_tracedecay_bin)" + tracedecay_bin="${TRACEDECAY_BIN:-$REPO_ROOT/bazel-bin/crates/tracedecay-cli/tracedecay}" if [[ -z "$tracedecay_bin" || ! -x "$tracedecay_bin" ]]; then - echo "error: no tracedecay binary found; build one or set TRACEDECAY_BIN" >&2 + echo "error: tracedecay binary not executable at $tracedecay_bin; run bazel build //crates/tracedecay-cli:tracedecay or set TRACEDECAY_BIN" >&2 return 2 fi tracedecay_bin="$(readlink -f "$tracedecay_bin")" diff --git a/scripts/mcp_probe/README.md b/scripts/mcp_probe/README.md index c570acd433..c5816a617a 100644 --- a/scripts/mcp_probe/README.md +++ b/scripts/mcp_probe/README.md @@ -31,8 +31,8 @@ Each module exposes a `probes_for(discovered)` function that returns ## Quick run ```sh -# 1. Build the release binary (driver shells out to ../../target/release/tracedecay) -cargo build -p tracedecay-cli --release --bin tracedecay +# 1. Build the release binary (driver uses bazel-bin/crates/tracedecay-cli/tracedecay) +bazel build --config=release //crates/tracedecay-cli:tracedecay # 2. Run the matrix python3 scripts/mcp_probe/probe.py diff --git a/scripts/mcp_probe/probe.py b/scripts/mcp_probe/probe.py index 5948869067..958f3b2ff9 100755 --- a/scripts/mcp_probe/probe.py +++ b/scripts/mcp_probe/probe.py @@ -34,7 +34,7 @@ ROOT = Path(__file__).resolve().parent REPO_ROOT = ROOT.parents[1] -DEFAULT_BIN = REPO_ROOT / "target" / "release" / "tracedecay" +DEFAULT_BIN = REPO_ROOT / "bazel-bin" / "crates" / "tracedecay-cli" / "tracedecay" DEFAULT_LOG = "/tmp/tracedecay_matrix.log" DEFAULT_REPOS = ROOT / "repos.toml" DEFAULT_STDERR_DIR = "/tmp/tracedecay_matrix_stderr" @@ -347,7 +347,7 @@ def load_probe_set(languages: list[str], discovered: dict) -> dict: def main() -> int: if not BIN.exists(): print(f"error: tracedecay binary not found at {BIN}", file=sys.stderr) - print(f"hint: run `cargo build --release` first", file=sys.stderr) + print("hint: run `bazel build --config=release //crates/tracedecay-cli:tracedecay` first", file=sys.stderr) return 2 sys.path.insert(0, str(ROOT)) diff --git a/scripts/opencode_stock_integration.sh b/scripts/opencode_stock_integration.sh index 5d80845f45..29238982c1 100755 --- a/scripts/opencode_stock_integration.sh +++ b/scripts/opencode_stock_integration.sh @@ -6,11 +6,11 @@ # Used by the `host-stock-integration` CI job and runnable locally: # # npm install --global @opencode/cli@ -# cargo build -p tracedecay-cli --bin tracedecay +# bazel build //crates/tracedecay-cli:tracedecay # scripts/opencode_stock_integration.sh # # Environment: -# TRACEDECAY_BIN tracedecay binary to install/test (default: target/debug/tracedecay) +# TRACEDECAY_BIN tracedecay binary to install/test (default: bazel-bin/crates/tracedecay-cli/tracedecay) # OPENCODE_BIN stock opencode binary (default: opencode on PATH) # # Everything runs in a throwaway HOME and a throwaway initialized project; no diff --git a/scripts/perf-gate.sh b/scripts/perf-gate.sh index c55b1492b4..9f46cb93db 100755 --- a/scripts/perf-gate.sh +++ b/scripts/perf-gate.sh @@ -99,7 +99,6 @@ PERF_REINDEX_WORKTREES="${PERF_REINDEX_WORKTREES:-0}" # Load shape. Overridable so a laptop can run a shorter pass than CI. PERF_WORKERS="${PERF_WORKERS:-6}" PERF_DURATION_SECONDS="${PERF_DURATION_SECONDS:-60}" -PERF_CARGO_PROFILE="${PERF_CARGO_PROFILE:-release}" PERF_DAEMON_READY_TIMEOUT="${PERF_DAEMON_READY_TIMEOUT:-300}" PERF_INDEX_TIMEOUT="${PERF_INDEX_TIMEOUT:-$((PERF_BUDGET_INDEX_SECONDS + 120))}" @@ -227,23 +226,20 @@ mkdir -p "$RUN_DIR/home/.local/share" "$RUN_DIR/home/.config" "$RUN_DIR/profile" mkdir -p "$PERF_OUTPUT_DIR" # ── PHASE BUILD ────────────────────────────────────────────────────────────── -# Runs before the environment is redirected so cargo keeps the real HOME, and -# with it the real ~/.cargo registry and toolchain. +# Build before redirecting the environment so Bazel keeps its configured cache. if [[ -n "${TRACEDECAY_PERF_BIN:-}" ]]; then [[ -x "$TRACEDECAY_PERF_BIN" ]] || die "TRACEDECAY_PERF_BIN '$TRACEDECAY_PERF_BIN' is not executable" BIN="$(cd "$(dirname "$TRACEDECAY_PERF_BIN")" && pwd)/$(basename "$TRACEDECAY_PERF_BIN")" log "==> PHASE BUILD: using prebuilt binary $BIN" else - command -v cargo >/dev/null 2>&1 || die "cargo is required unless TRACEDECAY_PERF_BIN is set" - log "==> PHASE BUILD: cargo build -p tracedecay-cli --profile $PERF_CARGO_PROFILE --bin tracedecay" - # No CARGO_TARGET_DIR override: .cargo/config.toml already pins a repo-local - # target dir, which is exactly what the CI cache restores. - (cd "$REPO_ROOT" && cargo build --locked -p tracedecay-cli --profile "$PERF_CARGO_PROFILE" --bin tracedecay) >&2 || + command -v bazel >/dev/null 2>&1 || die "bazel is required unless TRACEDECAY_PERF_BIN is set" + log "==> PHASE BUILD: bazel build --config=release //crates/tracedecay-cli:tracedecay" + (cd "$REPO_ROOT" && bazel build --config=release //crates/tracedecay-cli:tracedecay) >&2 || die "the tracedecay binary failed to build" - BUILD_DIR="$PERF_CARGO_PROFILE" - [[ "$BUILD_DIR" == "dev" ]] && BUILD_DIR="debug" - BIN="$REPO_ROOT/target/$BUILD_DIR/tracedecay" + BUILD_DIR="$(cd "$REPO_ROOT" && bazel info --config=release bazel-bin)" || + die "could not resolve the Bazel output directory" + BIN="$BUILD_DIR/crates/tracedecay-cli/tracedecay" [[ -x "$BIN" ]] || die "expected a binary at $BIN after the build" fi BUILD_VERSION="$("$BIN" --version 2>/dev/null | tr -d '\n' || echo unknown)" diff --git a/scripts/require-exact-test.sh b/scripts/require-exact-test.sh index e9e50f298a..f0dcd7caf5 100755 --- a/scripts/require-exact-test.sh +++ b/scripts/require-exact-test.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# Run one name-filtered cargo test and require that it actually ran. +# Run one name-filtered libtest target and require that it actually ran. # -# `cargo test -- --exact` exits 0 when the filter matches NOTHING: +# `bazel test TARGET --test_arg=NAME --test_arg=--exact` exits 0 when the filter matches NOTHING: # # running 0 tests # test result: ok. 0 passed; 0 failed; 0 ignored; 118 filtered out @@ -12,7 +12,8 @@ # nextest has --no-tests=fail for this; libtest does not, so assert on the count # libtest reports instead of on the exit code alone. # -# usage: scripts/require-exact-test.sh cargo test --test foo the_test -- --exact +# usage: scripts/require-exact-test.sh bazel test //crates/tracedecay-hooks:unit_test \ +# --test_output=all --test_arg=spool::tests::checksum_is_real_sha256 --test_arg=--exact # # Set REQUIRE_EXACT_TEST_COUNT to a positive integer, or to `nonzero` for a # single-target suite whose membership may evolve but must never become empty. @@ -27,7 +28,7 @@ fi if [ "$#" -eq 0 ]; then echo "usage: $0 " >&2 - echo "example: $0 cargo test --test foo the_test -- --exact" >&2 + echo "example: $0 bazel test //crates/tracedecay-hooks:unit_test --test_output=all --test_arg=spool::tests::checksum_is_real_sha256 --test_arg=--exact" >&2 exit 2 fi diff --git a/scripts/test-efficiency-scorecard.py b/scripts/test-efficiency-scorecard.py index 8055885ee9..dc3540db43 100644 --- a/scripts/test-efficiency-scorecard.py +++ b/scripts/test-efficiency-scorecard.py @@ -2,7 +2,9 @@ from __future__ import annotations import importlib.util +import subprocess import sys +import tempfile import unittest from pathlib import Path from types import ModuleType @@ -22,6 +24,55 @@ def load_scorecard() -> ModuleType: return module +class ScorecardBuildTests(unittest.TestCase): + def test_resolves_binary_from_configured_bazel_output(self) -> None: + scorecard = load_scorecard() + with tempfile.TemporaryDirectory(prefix="scorecard build ") as scratch: + output = Path(scratch) + binary = output / "crates" / "tracedecay-cli" / "tracedecay" + binary.parent.mkdir(parents=True) + binary.touch() + with mock.patch.object( + scorecard.subprocess, + "run", + side_effect=[ + subprocess.CompletedProcess([], 0), + subprocess.CompletedProcess([], 0, f"{output}\n", ""), + ], + ) as run: + self.assertEqual(scorecard.build_binary(), binary) + self.assertEqual( + [call.args[0] for call in run.call_args_list], + [ + ("bazel", "build", "--config=release", "//crates/tracedecay-cli:tracedecay"), + ("bazel", "info", "--config=release", "bazel-bin"), + ], + ) + + def test_failed_build_does_not_use_an_old_binary(self) -> None: + scorecard = load_scorecard() + with mock.patch.object( + scorecard.subprocess, "run", return_value=subprocess.CompletedProcess([], 1) + ) as run: + with self.assertRaisesRegex(scorecard.HarnessError, "release build failed"): + scorecard.build_binary() + self.assertEqual(run.call_count, 1) + + def test_failed_or_empty_output_discovery_is_an_error(self) -> None: + scorecard = load_scorecard() + for discovery in ( + subprocess.CompletedProcess([], 1, "", "configuration failed"), + subprocess.CompletedProcess([], 0, "", ""), + ): + with self.subTest(discovery=discovery), mock.patch.object( + scorecard.subprocess, + "run", + side_effect=[subprocess.CompletedProcess([], 0), discovery], + ): + with self.assertRaisesRegex(scorecard.HarnessError, "resolve the Bazel output"): + scorecard.build_binary() + + class FakeStatusObserver: def __init__( self, diff --git a/scripts/tool-sweep.sh b/scripts/tool-sweep.sh index 532a7f05af..4100b5ac6e 100755 --- a/scripts/tool-sweep.sh +++ b/scripts/tool-sweep.sh @@ -20,7 +20,7 @@ reported as failed rather than skipped. EOF } -BIN="${TRACEDECAY_BIN:-$REPO_ROOT/target/release/tracedecay}" +BIN="${TRACEDECAY_BIN:-$REPO_ROOT/bazel-bin/crates/tracedecay-cli/tracedecay}" OUT="" WHOLE_RUN_DEADLINE_MS="${TRACEDECAY_SWEEP_WHOLE_RUN_DEADLINE_MS:-1800000}" diff --git a/scripts/with-isolated-tracedecay-daemon.sh b/scripts/with-isolated-tracedecay-daemon.sh index 7f52042c9b..83e0563a92 100755 --- a/scripts/with-isolated-tracedecay-daemon.sh +++ b/scripts/with-isolated-tracedecay-daemon.sh @@ -9,7 +9,6 @@ usage() { cat >&2 <<'EOF' Usage: with-isolated-tracedecay-daemon.sh --bin PATH [options] -- COMMAND [ARG...] - with-isolated-tracedecay-daemon.sh --cargo DIR [options] -- COMMAND [ARG...] Options: --ready-timeout SECONDS Daemon readiness deadline (default: 60) @@ -27,15 +26,13 @@ EOF ready_timeout=60 stop_timeout=5 lifecycle_label="" -daemon_mode="" daemon_value="" while (($# > 0)); do case "$1" in - --bin | --cargo) + --bin) (($# >= 2)) || usage - [[ -z "$daemon_mode" ]] || usage - daemon_mode="${1#--}" + [[ -z "$daemon_value" ]] || usage daemon_value="$2" shift 2 ;; @@ -64,27 +61,16 @@ while (($# > 0)); do esac done -[[ -n "$daemon_mode" && $# -gt 0 ]] || usage +[[ -n "$daemon_value" && $# -gt 0 ]] || usage for value in "$ready_timeout" "$stop_timeout"; do [[ "$value" =~ ^[1-9][0-9]*$ ]] || usage done -case "$daemon_mode" in - bin) - [[ -x "$daemon_value" ]] || { - echo "error: tracedecay binary is not executable: $daemon_value" >&2 - exit 2 - } - daemon_value="$(cd "$(dirname "$daemon_value")" && pwd)/$(basename "$daemon_value")" - ;; - cargo) - [[ -f "$daemon_value/Cargo.toml" ]] || { - echo "error: Cargo.toml not found under: $daemon_value" >&2 - exit 2 - } - daemon_value="$(cd "$daemon_value" && pwd)" - ;; -esac +[[ -x "$daemon_value" ]] || { + echo "error: tracedecay binary is not executable: $daemon_value" >&2 + exit 2 +} +daemon_value="$(cd "$(dirname "$daemon_value")" && pwd)/$(basename "$daemon_value")" command -v python3 >/dev/null 2>&1 || { echo "error: python3 is required for the bounded daemon socket probe" >&2 @@ -163,17 +149,9 @@ trap 'exit 130' INT trap 'exit 143' TERM [[ -z "$lifecycle_label" ]] || echo "== starting $lifecycle_label" -if [[ "$daemon_mode" == "bin" ]]; then - python3 -S "$PROCESS_HELPER" exec-session --parent-pid "$$" -- \ - "$daemon_value" daemon run --socket "$TRACEDECAY_DAEMON_SOCKET" \ - >"$daemon_log" 2>&1 & -else - ( - cd "$daemon_value" - exec python3 -S "$PROCESS_HELPER" exec-session --parent-pid "$$" -- \ - cargo run -- daemon run --socket "$TRACEDECAY_DAEMON_SOCKET" - ) >"$daemon_log" 2>&1 & -fi +python3 -S "$PROCESS_HELPER" exec-session --parent-pid "$$" -- \ + "$daemon_value" daemon run --socket "$TRACEDECAY_DAEMON_SOCKET" \ + >"$daemon_log" 2>&1 & daemon_pid=$! # `exec-session` execs into the daemon, so this is the daemon's own PID. The # smoke command samples its resident memory per readiness probe; a timeout or From a93682cc489404981963afa97b01ede3bed60fd7 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 15:46:48 -0700 Subject: [PATCH 18/29] fix(bazel): execute custom benchmark harnesses with declared data --- benchmark_data/BUILD.bazel | 10 + benchmark_data/claude-observation/README.md | 6 +- .../claude-observation/workload-v1.json | 2 +- benchmark_data/session-temporal/README.md | 5 +- .../session-temporal/workload-v1.json | 9 - crates/tracedecay-application/BUILD.bazel | 1 + crates/tracedecay-code-index/BUILD.bazel | 7 + crates/tracedecay-global-db/BUILD.bazel | 3 + crates/tracedecay-graph-db/BUILD.bazel | 5 + crates/tracedecay-query/BUILD.bazel | 1 + crates/tracedecay-runtime-core/BUILD.bazel | 2 + crates/tracedecay-sessions/BUILD.bazel | 2 + crates/tracedecay/BUILD.bazel | 9 +- crates/tracedecay/benches/session_temporal.rs | 3 +- .../benches/session_temporal/harness.rs | 46 ++--- .../claude_observation_benchmark/artifact.rs | 175 +++++------------- .../claude_observation_benchmark/main.rs | 18 +- .../claude_observation_benchmark/model.rs | 10 +- .../claude_observation_benchmark/runner.rs | 12 +- scripts/bazel/gen_builds.py | 15 ++ scripts/run-claude-observation-benchmark.sh | 151 +++------------ scripts/run-session-temporal-benchmark.sh | 41 ++-- 22 files changed, 168 insertions(+), 365 deletions(-) diff --git a/benchmark_data/BUILD.bazel b/benchmark_data/BUILD.bazel index 21276f5e9f..9aaa30ac94 100644 --- a/benchmark_data/BUILD.bazel +++ b/benchmark_data/BUILD.bazel @@ -31,4 +31,14 @@ filegroup( exports_files(["runtime/fixtures/project/tests/catalog.rs"], visibility = ["//visibility:public"], ) + +filegroup( + name = "session-temporal", + srcs = glob(["session-temporal/**"]), + visibility = ["//visibility:public"], +) + +exports_files(["session-temporal/workload-v1.json"], + visibility = ["//visibility:public"], +) # END generated by scripts/bazel/gen_builds.py diff --git a/benchmark_data/claude-observation/README.md b/benchmark_data/claude-observation/README.md index 017c6526d6..0f07d771e3 100644 --- a/benchmark_data/claude-observation/README.md +++ b/benchmark_data/claude-observation/README.md @@ -63,8 +63,10 @@ The legacy schema records real SHA-256 identities for workload members and native fixtures plus the host target, Rust/Cargo versions, kernel, and hardware. Those source/content digests remain useful provenance. Do not recreate the former clean-checkout archive, tracked-source snapshot, compiler-input -attestation, or evidence-only commit workflow. Run ordinary Cargo against the -working checkout and report the source revision and dirty state truthfully. +attestation, or evidence-only commit workflow. The runner uses Bazel `--config=release` in the current clean checkout and +records the source revision before and after measurement, the Bazel and Rust +versions, and the measured executable identity. It validates new output before +publishing the result and index, without archive or backup copies. Linux `/proc` is the explicit measurement platform contract. Preflight requires all measured interfaces, a successful write of `5` to diff --git a/benchmark_data/claude-observation/workload-v1.json b/benchmark_data/claude-observation/workload-v1.json index 63705aadbe..54ed38a105 100644 --- a/benchmark_data/claude-observation/workload-v1.json +++ b/benchmark_data/claude-observation/workload-v1.json @@ -652,6 +652,6 @@ ] } }, - "command": "cargo test --quiet --release -p tracedecay --features test-helpers --test claude_observation_benchmark production_observation_pipeline_baseline -- --ignored --exact --nocapture --test-threads=1", + "command": "scripts/run-claude-observation-benchmark.sh", "evidence_runner": "scripts/run-claude-observation-benchmark.sh" } diff --git a/benchmark_data/session-temporal/README.md b/benchmark_data/session-temporal/README.md index 930ce9263b..7745d20acb 100644 --- a/benchmark_data/session-temporal/README.md +++ b/benchmark_data/session-temporal/README.md @@ -37,10 +37,9 @@ Runtime sanitizer provenance for those fixtures is in scripts/run-session-temporal-benchmark.sh --dry-run scripts/run-session-temporal-benchmark.sh --run # diagnostic on Linux or macOS scripts/run-session-temporal-benchmark.sh --refresh-contract # Linux only -cargo bench --bench session_temporal --all-features -- --run ``` -Dry-run is Cargo-free. `--run` isolates `HOME` and `TRACEDECAY_DATA_DIR` and +Dry-run performs no build. The measurement runner uses Bazel `--config=release`. `--run` isolates `HOME` and `TRACEDECAY_DATA_DIR` and measures: `rebuild_activate`, `exact_replay`, `compact_rank`, and `late_hydrate`. `--run` prints diagnostic samples but never changes checked-in evidence. `--refresh-contract` is Linux-only and the only publishing path: it requires a @@ -57,7 +56,7 @@ record counts. Recorded only when executed in the same capture window: ```bash -cargo test --test session_suite --all-features temporal_derived_evidence:: -- --test-threads=1 +bazel test --config=ci //crates/tracedecay:session_suite --test_arg=temporal_derived_evidence:: --test_arg=--test-threads=1 ``` Quantiles are descriptive nearest-rank sample labels, not inferential claims. diff --git a/benchmark_data/session-temporal/workload-v1.json b/benchmark_data/session-temporal/workload-v1.json index 9b4c53203f..7b5683fc25 100644 --- a/benchmark_data/session-temporal/workload-v1.json +++ b/benchmark_data/session-temporal/workload-v1.json @@ -49,15 +49,6 @@ "SessionRetrievalService with GlobalDbSessionTemporalExecution and AllSessionsInAuthorizedRoot hydration" ] }, - "profile": { - "cargo_profile": "bench", - "debug": false, - "debug_assertions": false, - "incremental": false, - "manifest_path": "Cargo.toml", - "opt_level": 3, - "overflow_checks": false - }, "runner": { "path": "scripts/run-session-temporal-benchmark.sh" }, diff --git a/crates/tracedecay-application/BUILD.bazel b/crates/tracedecay-application/BUILD.bazel index ddf35d088d..c89e59fc36 100644 --- a/crates/tracedecay-application/BUILD.bazel +++ b/crates/tracedecay-application/BUILD.bazel @@ -108,6 +108,7 @@ rust_test( rust_test( name = "work_rollup", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]) + glob(["tests/application_suite/**/*.rs", "tests/application_suite/github_stack_coordinator/**/*.rs"]), crate_root = "benches/work_rollup.rs", edition = crate_edition(), diff --git a/crates/tracedecay-code-index/BUILD.bazel b/crates/tracedecay-code-index/BUILD.bazel index 6b53b9d04d..55001f821f 100644 --- a/crates/tracedecay-code-index/BUILD.bazel +++ b/crates/tracedecay-code-index/BUILD.bazel @@ -159,6 +159,7 @@ rust_test( rust_test( name = "code_index_chunks", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/code_index_chunks.rs", edition = crate_edition(), @@ -174,6 +175,7 @@ rust_test( rust_test( name = "git_ancestry", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/git_ancestry.rs", edition = crate_edition(), @@ -189,6 +191,7 @@ rust_test( rust_test( name = "partitioned_codec", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/partitioned_codec.rs", edition = crate_edition(), @@ -204,6 +207,7 @@ rust_test( rust_test( name = "restore_generation", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/restore_generation.rs", edition = crate_edition(), @@ -219,6 +223,7 @@ rust_test( rust_test( name = "retained_parse", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/retained_parse.rs", edition = crate_edition(), @@ -234,6 +239,7 @@ rust_test( rust_test( name = "sealed_storage", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/sealed_storage.rs", edition = crate_edition(), @@ -249,6 +255,7 @@ rust_test( rust_test( name = "source_search", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/source_search.rs", edition = crate_edition(), diff --git a/crates/tracedecay-global-db/BUILD.bazel b/crates/tracedecay-global-db/BUILD.bazel index d037760f2b..1ef35f66d5 100644 --- a/crates/tracedecay-global-db/BUILD.bazel +++ b/crates/tracedecay-global-db/BUILD.bazel @@ -125,6 +125,7 @@ rust_test( rust_test( name = "lcm_expansion", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/lcm_expansion.rs", edition = crate_edition(), @@ -140,6 +141,7 @@ rust_test( rust_test( name = "session_activity_reads", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/session_activity_reads.rs", edition = crate_edition(), @@ -155,6 +157,7 @@ rust_test( rust_test( name = "stack_delivery_batch", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/stack_delivery_batch.rs", edition = crate_edition(), diff --git a/crates/tracedecay-graph-db/BUILD.bazel b/crates/tracedecay-graph-db/BUILD.bazel index 00b806622d..8e1b496f21 100644 --- a/crates/tracedecay-graph-db/BUILD.bazel +++ b/crates/tracedecay-graph-db/BUILD.bazel @@ -166,6 +166,7 @@ rust_test( rust_test( name = "code_traversal", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/code_traversal.rs", edition = crate_edition(), @@ -181,6 +182,7 @@ rust_test( rust_test( name = "mixed_read_write", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/mixed_read_write.rs", edition = crate_edition(), @@ -196,6 +198,7 @@ rust_test( rust_test( name = "native_state_100k", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/native_state_100k.rs", edition = crate_edition(), @@ -211,6 +214,7 @@ rust_test( rust_test( name = "publication_scale", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/publication_scale.rs", edition = crate_edition(), @@ -226,6 +230,7 @@ rust_test( rust_test( name = "replay_after_mismatch", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/replay_after_mismatch.rs", edition = crate_edition(), diff --git a/crates/tracedecay-query/BUILD.bazel b/crates/tracedecay-query/BUILD.bazel index c44e061194..c0b084719d 100644 --- a/crates/tracedecay-query/BUILD.bazel +++ b/crates/tracedecay-query/BUILD.bazel @@ -257,6 +257,7 @@ rust_test( rust_test( name = "code_lexical_catchup", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/code_lexical_catchup.rs", edition = crate_edition(), diff --git a/crates/tracedecay-runtime-core/BUILD.bazel b/crates/tracedecay-runtime-core/BUILD.bazel index 1dd5a08386..b4936b86e5 100644 --- a/crates/tracedecay-runtime-core/BUILD.bazel +++ b/crates/tracedecay-runtime-core/BUILD.bazel @@ -136,6 +136,7 @@ rust_test( rust_test( name = "exact_sql_writer_workload", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/exact_sql_writer_workload.rs", edition = crate_edition(), @@ -151,6 +152,7 @@ rust_test( rust_test( name = "git_repository_authority_bin", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/git_repository_authority.rs", edition = crate_edition(), diff --git a/crates/tracedecay-sessions/BUILD.bazel b/crates/tracedecay-sessions/BUILD.bazel index c97c015552..d07fe70a63 100644 --- a/crates/tracedecay-sessions/BUILD.bazel +++ b/crates/tracedecay-sessions/BUILD.bazel @@ -61,6 +61,7 @@ rust_library( rust_test( name = "cursor_dispatch_model", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/cursor_dispatch_model.rs", edition = crate_edition(), @@ -76,6 +77,7 @@ rust_test( rust_test( name = "host_transcript_io", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/host_transcript_io.rs", edition = crate_edition(), diff --git a/crates/tracedecay/BUILD.bazel b/crates/tracedecay/BUILD.bazel index e752f261f6..53b2ac3a31 100644 --- a/crates/tracedecay/BUILD.bazel +++ b/crates/tracedecay/BUILD.bazel @@ -136,7 +136,7 @@ rust_test( rustc_flags = perf_rustc_flags(1), crate_features = ["default", "full", "lite", "production", "rmcp-benchmark", "test-helpers", "test-transport", "token-counting"], compile_data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"] + ["//benchmark_data:claude-observation"], - data = glob(["tests/claude_observation_benchmark/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], + data = glob(["tests/claude_observation_benchmark/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"] + ["//benchmark_data:claude-observation"], ) rust_test( @@ -394,6 +394,7 @@ rust_test( rust_test( name = "large_repos", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/large_repos.rs", edition = crate_edition(), @@ -409,6 +410,7 @@ rust_test( rust_test( name = "session_temporal", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/session_temporal.rs", edition = crate_edition(), @@ -419,11 +421,12 @@ rust_test( rustc_flags = perf_rustc_flags(1), crate_features = ["default", "full", "lite", "production", "rmcp-benchmark", "test-helpers", "test-transport", "token-counting"], compile_data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"] + ["//benchmark_data:runtime_fixtures_project_src", "//benchmark_data:runtime_fixtures_project_tests", "//crates/tracedecay-hooks:fixtures_host_events_kimi"], - data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], + data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"] + ["//benchmark_data:session-temporal"], ) rust_test( name = "transcript_ingest", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/transcript_ingest.rs", edition = crate_edition(), @@ -439,6 +442,7 @@ rust_test( rust_test( name = "rmcp_connection_pipeline", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/rmcp_connection_pipeline.rs", edition = crate_edition(), @@ -454,6 +458,7 @@ rust_test( rust_test( name = "mcp_server_construction", + use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/mcp_server_construction.rs", edition = crate_edition(), diff --git a/crates/tracedecay/benches/session_temporal.rs b/crates/tracedecay/benches/session_temporal.rs index 9790cc3c7b..890041b341 100644 --- a/crates/tracedecay/benches/session_temporal.rs +++ b/crates/tracedecay/benches/session_temporal.rs @@ -37,8 +37,7 @@ fn main() { }) } _ => Err( - "usage: cargo bench -p tracedecay --bench session_temporal --features test-helpers [-- --run|--refresh-contract]" - .to_owned(), + "usage: scripts/run-session-temporal-benchmark.sh --run|--refresh-contract".to_owned(), ), }; if let Err(error) = result { diff --git a/crates/tracedecay/benches/session_temporal/harness.rs b/crates/tracedecay/benches/session_temporal/harness.rs index 602b744705..eb9b4f1efa 100644 --- a/crates/tracedecay/benches/session_temporal/harness.rs +++ b/crates/tracedecay/benches/session_temporal/harness.rs @@ -320,7 +320,6 @@ pub fn validate_contract() -> BenchResult<()> { json!(RUNNER_PATH), "runner path", )?; - validate_bench_profile(&root)?; validate_sanitization_receipt(&root)?; let index = read_json(&root.join(EVIDENCE_INDEX_PATH))?; @@ -519,7 +518,6 @@ fn validate_refresh_inputs(root: &Path, workload: &Value) -> BenchResult<()> { json!(true), "production path availability", )?; - validate_bench_profile(root)?; validate_sanitization_receipt(root) } @@ -535,7 +533,7 @@ pub async fn run_measurement() -> BenchResult { ); } validate_contract()?; - validate_bench_profile_enforced()?; + validate_measurement_configuration()?; let root = repository_root(); let measurement = Box::pin(capture_measurement()).await?; @@ -556,7 +554,7 @@ pub async fn refresh_contract() -> BenchResult { if !host_policy.allows_contract_refresh() { return Err("Session-temporal contract refresh is Linux-hosted".to_owned()); } - validate_bench_profile_enforced()?; + validate_measurement_configuration()?; let root = repository_root(); let workload = read_json(&root.join(WORKLOAD_PATH))?; validate_refresh_inputs(&root, &workload)?; @@ -1090,31 +1088,10 @@ fn validate_sanitization_receipt(root: &Path) -> BenchResult<()> { Ok(()) } -fn validate_bench_profile(root: &Path) -> BenchResult<()> { - let manifest = fs::read_to_string(root.join("Cargo.toml")) - .map_err(|error| format!("read Cargo.toml: {error}"))?; - let profile = manifest - .split_once("[profile.bench]") - .map(|(_, profile)| profile.split("\n[").next().unwrap_or(profile)) - .ok_or_else(|| "Cargo.toml is missing [profile.bench]".to_owned())?; - for line in [ - "opt-level = 3", - "debug = false", - "debug-assertions = false", - "overflow-checks = false", - "incremental = false", - ] { - if !profile.lines().any(|candidate| candidate.trim() == line) { - return Err(format!("bench profile is missing {line:?}")); - } - } - Ok(()) -} - -fn validate_bench_profile_enforced() -> BenchResult<()> { +fn validate_measurement_configuration() -> BenchResult<()> { if cfg!(debug_assertions) { return Err( - "optimized bench profile required: debug_assertions are enabled (use cargo bench)" + "optimized bench profile required: debug_assertions are enabled (use Bazel --config=release)" .to_owned(), ); } @@ -1160,11 +1137,16 @@ fn require_json_value(actual: &Value, expected: Value, label: &str) -> BenchResu fn repository_root() -> PathBuf { // The product package sits at `crates/tracedecay`; this benchmark reads git // metadata and fixtures that live at the workspace root above it. - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .and_then(Path::parent) - .expect("workspace root above crates/tracedecay") - .to_owned() + if let Some(root) = std::env::var_os("TRACEDECAY_BENCHMARK_REPO_ROOT") { + return PathBuf::from(root); + } + PathBuf::from( + std::env::var_os("CARGO_MANIFEST_DIR").unwrap_or_else(|| env!("CARGO_MANIFEST_DIR").into()), + ) + .parent() + .and_then(Path::parent) + .expect("workspace root above crates/tracedecay") + .to_owned() } fn current_commit(root: &Path) -> BenchResult { diff --git a/crates/tracedecay/tests/claude_observation_benchmark/artifact.rs b/crates/tracedecay/tests/claude_observation_benchmark/artifact.rs index f63658b160..84d5f7338f 100644 --- a/crates/tracedecay/tests/claude_observation_benchmark/artifact.rs +++ b/crates/tracedecay/tests/claude_observation_benchmark/artifact.rs @@ -19,16 +19,13 @@ use super::model::{ ProviderBenchmarkSuiteResult, ProviderPhaseResult, RawPhaseSample, WorkloadIdentity, }; use super::{ - BENCHMARK_COMMAND, BUILD_CARGO_CONFIG_IDENTITY, BUILD_CARGO_VERSION, BUILD_COMMIT, - BUILD_PROFILE, BUILD_RUSTC_VERSION, BUILD_RUSTC_WORKSPACE_WRAPPER, BUILD_RUSTC_WRAPPER, - BUILD_RUSTFLAGS, BUILD_SOURCE_MANIFEST_SHA256, BUILD_SOURCE_MODE, BUILD_TARGET_TRIPLE, - BUILD_TREE, HARNESS_SOURCES, MEASURED_REPETITIONS, NATIVE_PROVIDER_FIXTURES, + BENCHMARK_COMMAND, HARNESS_SOURCES, MEASURED_REPETITIONS, NATIVE_PROVIDER_FIXTURES, PROVIDER_PIPELINE_SCOPE, RECORDS_PER_REPETITION, RESULT_SCHEMA_VERSION, RETIRED_WORKLOAD_ID, WARMUP_REPETITIONS, WORKLOAD_ID, WORKLOAD_MANIFEST, WORKLOAD_MANIFEST_PATH, WORKLOAD_SCHEMA_VERSION, }; -pub(super) struct AttestedBuild { +pub(super) struct MeasuredBuild { pub(super) evidence_status: EvidenceStatus, pub(super) build_identity: BuildIdentity, } @@ -241,24 +238,18 @@ fn validate_acceptance_result(result: &BenchmarkResult) -> Result<(), String> { || !is_lower_hex(&result.build_identity.commit, 40) || !is_lower_hex(&result.build_identity.tree, 40) || result.build_identity.profile != "release" - || result.build_identity.source_mode != "git_archive_read_only_v1" - || !is_lower_hex(&result.build_identity.source_manifest_sha256, 64) - || result.build_identity.source_file_count == 0 + || result.build_identity.source_mode != "clean_git_worktree_v1" || result.build_identity.target_triple.is_empty() || result.build_identity.rustc_version != result.rustc - || result.build_identity.cargo_version != result.cargo - || result.build_identity.rustflags != "normalized-empty" - || result.build_identity.rustc_wrapper.is_empty() - || result.build_identity.rustc_workspace_wrapper.is_empty() - || !is_lower_hex(&result.build_identity.cargo_config_identity, 40) - || result.build_identity.data_root_basis != "current_executable_parent" + || result.build_identity.bazel_version != result.bazel + || result.build_identity.data_root_basis != "TEST_TMPDIR" || !is_lower_hex(&result.build_identity.executable_sha256, 64) || result.build_identity.executable_size_bytes == 0 { - return Err("acceptance result build attestation is invalid".to_string()); + return Err("acceptance result build identity is invalid".to_string()); } if result.rustc.is_empty() - || result.cargo.is_empty() + || result.bazel.is_empty() || result.kernel.is_empty() || result.cpu_identity.is_empty() || result.logical_cpu_count == 0 @@ -689,70 +680,41 @@ pub(super) fn sha256_file(path: &Path) -> std::io::Result<(String, u64)> { Ok((hex::encode(digest.finalize()), size)) } -pub(super) fn validate_release_profile( - debug_assertions: bool, - profile: Option<&str>, -) -> Result<(), String> { - if debug_assertions { - return Err("benchmark evidence cannot run with debug assertions".to_string()); - } - if profile != Some("release") { - return Err("benchmark evidence requires release build attestation".to_string()); +pub(super) fn measure_build_identity(git: &GitSnapshot) -> Result { + if cfg!(debug_assertions) { + return Err("benchmark requires Bazel --config=release".to_owned()); } - Ok(()) -} - -pub(super) fn attest_build(git: &GitSnapshot) -> AttestedBuild { - validate_release_profile(cfg!(debug_assertions), BUILD_PROFILE) - .expect("benchmark must use the release evidence runner"); - let commit = BUILD_COMMIT.expect("missing build-time Git commit attestation"); - let tree = BUILD_TREE.expect("missing build-time Git tree attestation"); - assert_eq!(commit, git.commit, "build commit differs from runtime HEAD"); - assert_eq!(tree, git.tree, "build tree differs from runtime HEAD tree"); let executable = fs::canonicalize(std::env::current_exe().expect("resolve executable")) .expect("canonicalize executable"); let (executable_sha256, executable_size_bytes) = sha256_file(&executable).expect("hash benchmark executable"); - let rustc_version = BUILD_RUSTC_VERSION.expect("missing build-time rustc identity"); - let cargo_version = BUILD_CARGO_VERSION.expect("missing build-time Cargo identity"); - assert_eq!(rustc_version, command_output("rustc", &["-Vv"])); - assert_eq!(cargo_version, command_output("cargo", &["-V"])); - let (_, source_file_count) = validate_source_archive(); - AttestedBuild { + let rustc_version = command_output("rustc", &["-Vv"]); + let target_triple = rustc_version + .lines() + .find_map(|line| line.strip_prefix("host: ")) + .expect("rustc host target") + .to_owned(); + assert!( + std::env::var_os("TEST_TMPDIR").is_some(), + "benchmark requires Bazel test isolation" + ); + let bazel_version = std::env::var("TRACEDECAY_BENCHMARK_BAZEL_VERSION") + .expect("run benchmark through its Bazel runner"); + Ok(MeasuredBuild { evidence_status: EvidenceStatus::Acceptance, build_identity: BuildIdentity { - commit: commit.to_string(), - tree: tree.to_string(), + commit: git.commit.clone(), + tree: git.tree.clone(), profile: "release".to_string(), - source_mode: BUILD_SOURCE_MODE - .expect("missing immutable source mode") - .to_string(), - source_manifest_sha256: BUILD_SOURCE_MANIFEST_SHA256 - .expect("missing source manifest identity") - .to_string(), - source_file_count, - target_triple: BUILD_TARGET_TRIPLE - .expect("missing build target triple") - .to_string(), - rustc_version: rustc_version.to_string(), - cargo_version: cargo_version.to_string(), - rustflags: BUILD_RUSTFLAGS - .expect("missing normalized Rust flags") - .to_string(), - rustc_wrapper: BUILD_RUSTC_WRAPPER - .expect("missing rustc wrapper identity") - .to_string(), - rustc_workspace_wrapper: BUILD_RUSTC_WORKSPACE_WRAPPER - .expect("missing workspace wrapper identity") - .to_string(), - cargo_config_identity: BUILD_CARGO_CONFIG_IDENTITY - .expect("missing Cargo configuration identity") - .to_string(), - data_root_basis: "current_executable_parent".to_string(), + source_mode: "clean_git_worktree_v1".to_string(), + target_triple, + rustc_version, + bazel_version, + data_root_basis: "TEST_TMPDIR".to_string(), executable_sha256, executable_size_bytes, }, - } + }) } pub(super) fn validate_git_snapshots( @@ -869,9 +831,6 @@ pub(super) fn command_output(command: &str, args: &[&str]) -> String { } pub(super) fn git_snapshot() -> GitSnapshot { - if BUILD_SOURCE_MODE == Some("git_archive_read_only_v1") { - return validate_source_archive().0; - } verify_git_toplevel(); GitSnapshot { commit: git_output(&["rev-parse", "HEAD"]), @@ -880,56 +839,6 @@ pub(super) fn git_snapshot() -> GitSnapshot { } } -fn validate_source_archive() -> (GitSnapshot, usize) { - let manifest_path = repository_root().join(".tracedecay-benchmark-source-manifest"); - let manifest = fs::read(&manifest_path).expect("read immutable source manifest"); - assert_eq!( - sha256_hex(&manifest), - BUILD_SOURCE_MANIFEST_SHA256.expect("missing source manifest identity"), - "source manifest differs from build attestation" - ); - let manifest = String::from_utf8(manifest).expect("source manifest is UTF-8"); - let mut source_file_count = 0; - for line in manifest.lines() { - let mut fields = line.splitn(3, '\t'); - let mode = fields.next().expect("source manifest mode"); - let digest = fields.next().expect("source manifest digest"); - let relative = fields.next().expect("source manifest path"); - if mode == "160000" { - continue; - } - let path = repository_root().join(relative); - let bytes = fs::read(&path) - .unwrap_or_else(|error| panic!("read immutable compiler input {relative}: {error}")); - assert_eq!( - sha256_hex(&bytes), - digest, - "compiler input changed: {relative}" - ); - assert!( - fs::metadata(&path) - .expect("compiler input metadata") - .permissions() - .readonly(), - "compiler input is writable: {relative}" - ); - source_file_count += 1; - } - assert!(source_file_count > 0, "source manifest contains no files"); - ( - GitSnapshot { - commit: BUILD_COMMIT - .expect("missing source commit identity") - .to_string(), - tree: BUILD_TREE - .expect("missing source tree identity") - .to_string(), - dirty: false, - }, - source_file_count, - ) -} - fn worktree_is_dirty() -> bool { let output = Command::new("git") .args([ @@ -952,17 +861,21 @@ pub(super) fn status_output_is_dirty(output: &[u8]) -> bool { /// The product package directory. `HARNESS_SOURCES` records paths relative to /// the package (`tests/claude_observation_benchmark/...`), a different anchor from the /// workspace-level fixtures and benchmark data below. -pub(super) fn package_root() -> &'static Path { - Path::new(env!("CARGO_MANIFEST_DIR")) +pub(super) fn package_root() -> PathBuf { + repository_root().join("crates/tracedecay") } -pub(super) fn repository_root() -> &'static Path { - // The product package sits at `crates/tracedecay`; the fixtures, benchmark - // data, and git metadata this module reads all live at the workspace root. - Path::new(env!("CARGO_MANIFEST_DIR")) - .parent() - .and_then(Path::parent) - .expect("workspace root above crates/tracedecay") +pub(super) fn repository_root() -> PathBuf { + if let Some(root) = std::env::var_os("TRACEDECAY_BENCHMARK_REPO_ROOT") { + return PathBuf::from(root); + } + PathBuf::from( + std::env::var_os("CARGO_MANIFEST_DIR").unwrap_or_else(|| env!("CARGO_MANIFEST_DIR").into()), + ) + .parent() + .and_then(Path::parent) + .expect("workspace root above crates/tracedecay") + .to_owned() } fn git_output(args: &[&str]) -> String { diff --git a/crates/tracedecay/tests/claude_observation_benchmark/main.rs b/crates/tracedecay/tests/claude_observation_benchmark/main.rs index d8a18f8b31..f885d8763b 100644 --- a/crates/tracedecay/tests/claude_observation_benchmark/main.rs +++ b/crates/tracedecay/tests/claude_observation_benchmark/main.rs @@ -19,7 +19,7 @@ const WARMUP_REPETITIONS: usize = 3; const MEASURED_REPETITIONS: usize = 30; const RECORDS_PER_REPETITION: usize = 64; const CONCURRENCY: usize = 1; -const BENCHMARK_COMMAND: &str = "cargo test --quiet --release -p tracedecay --features test-helpers --test claude_observation_benchmark production_observation_pipeline_baseline -- --ignored --exact --nocapture --test-threads=1"; +const BENCHMARK_COMMAND: &str = "scripts/run-claude-observation-benchmark.sh"; const EVIDENCE_RUNNER: &str = "scripts/run-claude-observation-benchmark.sh"; const WORKLOAD_IMPLEMENTATION: &str = "tests/claude_observation_benchmark/main.rs"; const WORKLOAD_MANIFEST_PATH: &str = "benchmark_data/claude-observation/workload-v1.json"; @@ -113,22 +113,6 @@ const HARNESS_SOURCES: &[(&str, &str)] = &[ include_str!("tests.rs"), ), ]; -const BUILD_COMMIT: Option<&str> = option_env!("TRACEDECAY_BENCHMARK_BUILD_COMMIT"); -const BUILD_TREE: Option<&str> = option_env!("TRACEDECAY_BENCHMARK_BUILD_TREE"); -const BUILD_PROFILE: Option<&str> = option_env!("TRACEDECAY_BENCHMARK_BUILD_PROFILE"); -const BUILD_SOURCE_MODE: Option<&str> = option_env!("TRACEDECAY_BENCHMARK_BUILD_SOURCE_MODE"); -const BUILD_SOURCE_MANIFEST_SHA256: Option<&str> = - option_env!("TRACEDECAY_BENCHMARK_SOURCE_MANIFEST_SHA256"); -const BUILD_TARGET_TRIPLE: Option<&str> = option_env!("TRACEDECAY_BENCHMARK_BUILD_TARGET_TRIPLE"); -const BUILD_RUSTC_VERSION: Option<&str> = option_env!("TRACEDECAY_BENCHMARK_BUILD_RUSTC_VERSION"); -const BUILD_CARGO_VERSION: Option<&str> = option_env!("TRACEDECAY_BENCHMARK_BUILD_CARGO_VERSION"); -const BUILD_RUSTFLAGS: Option<&str> = option_env!("TRACEDECAY_BENCHMARK_BUILD_RUSTFLAGS"); -const BUILD_RUSTC_WRAPPER: Option<&str> = option_env!("TRACEDECAY_BENCHMARK_BUILD_RUSTC_WRAPPER"); -const BUILD_RUSTC_WORKSPACE_WRAPPER: Option<&str> = - option_env!("TRACEDECAY_BENCHMARK_BUILD_RUSTC_WORKSPACE_WRAPPER"); -const BUILD_CARGO_CONFIG_IDENTITY: Option<&str> = - option_env!("TRACEDECAY_BENCHMARK_BUILD_CARGO_CONFIG_IDENTITY"); - #[tokio::test] #[ignore = "release-mode claude-observation performance baseline; run the documented exact command"] async fn production_observation_pipeline_baseline() { diff --git a/crates/tracedecay/tests/claude_observation_benchmark/model.rs b/crates/tracedecay/tests/claude_observation_benchmark/model.rs index b64001cbae..205c5d65b0 100644 --- a/crates/tracedecay/tests/claude_observation_benchmark/model.rs +++ b/crates/tracedecay/tests/claude_observation_benchmark/model.rs @@ -233,15 +233,9 @@ pub(super) struct BuildIdentity { pub(super) tree: String, pub(super) profile: String, pub(super) source_mode: String, - pub(super) source_manifest_sha256: String, - pub(super) source_file_count: usize, pub(super) target_triple: String, pub(super) rustc_version: String, - pub(super) cargo_version: String, - pub(super) rustflags: String, - pub(super) rustc_wrapper: String, - pub(super) rustc_workspace_wrapper: String, - pub(super) cargo_config_identity: String, + pub(super) bazel_version: String, pub(super) data_root_basis: String, pub(super) executable_sha256: String, pub(super) executable_size_bytes: u64, @@ -259,7 +253,7 @@ pub(super) struct BenchmarkResult { pub(super) git_after: GitSnapshot, pub(super) command: String, pub(super) rustc: String, - pub(super) cargo: String, + pub(super) bazel: String, pub(super) kernel: String, pub(super) cpu_identity: String, pub(super) logical_cpu_count: usize, diff --git a/crates/tracedecay/tests/claude_observation_benchmark/runner.rs b/crates/tracedecay/tests/claude_observation_benchmark/runner.rs index 7cdc6eac2e..50d6c87e6d 100644 --- a/crates/tracedecay/tests/claude_observation_benchmark/runner.rs +++ b/crates/tracedecay/tests/claude_observation_benchmark/runner.rs @@ -40,7 +40,7 @@ fn ensure_background_cpu_authority() { } use super::artifact::{ - attest_build, command_output, git_snapshot, validate_git_snapshots, workload_identity, + command_output, git_snapshot, measure_build_identity, validate_git_snapshots, workload_identity, }; use super::metrics::{ aggregate_samples, cpu_identity, elapsed_ns, memory_total_kib, preflight_platform, @@ -1267,7 +1267,8 @@ pub(super) async fn run() { "benchmark evidence requires a clean worktree before execution" ); let identity_before = workload_identity(); - let attested_build = attest_build(&git_before); + let measured_build = measure_build_identity(&git_before) + .expect("benchmark must use the release evidence runner"); for repetition in 0..WARMUP_REPETITIONS { let fixture = Fixture::new(repetition).await; let source = fixture.source(); @@ -1373,14 +1374,15 @@ pub(super) async fn run() { let result = BenchmarkResult { schema_version: RESULT_SCHEMA_VERSION, workload_id: WORKLOAD_ID.to_string(), - evidence_status: attested_build.evidence_status, + evidence_status: measured_build.evidence_status, workload_identity: identity_before, - build_identity: attested_build.build_identity, + build_identity: measured_build.build_identity, git_before, git_after, command: BENCHMARK_COMMAND.to_string(), rustc: command_output("rustc", &["-Vv"]), - cargo: command_output("cargo", &["-V"]), + bazel: std::env::var("TRACEDECAY_BENCHMARK_BAZEL_VERSION") + .expect("benchmark Bazel version"), kernel: command_output("uname", &["-srmo"]), cpu_identity: cpu_identity(), logical_cpu_count: std::thread::available_parallelism() diff --git a/scripts/bazel/gen_builds.py b/scripts/bazel/gen_builds.py index 124c68bbc5..c4a2e37ebe 100755 --- a/scripts/bazel/gen_builds.py +++ b/scripts/bazel/gen_builds.py @@ -802,6 +802,7 @@ def needs_env_tmpdir(p, t, extra_dirs=()): def render_build(p): name = p["name"] + manifest = tomllib.loads(Path(p["manifest_path"]).read_text()) test_sizes = (p.get("metadata") or {}).get("bazel", {}).get("test_sizes", {}) if not isinstance(test_sizes, dict) or any( size not in ("small", "medium", "large", "enormous") @@ -1057,6 +1058,11 @@ def unique_name(want): kinds = t["kind"] if not ({"test", "bench"} & set(kinds)): continue + declared_target = next( + (target for kind in kinds for target in manifest.get(kind, []) + if target.get("name") == t["name"]), + {}, + ) req = frozenset(t.get("required-features") or []) fmap = contexts[(name, "normal,build,dev", req)] deps = dedup( @@ -1093,6 +1099,13 @@ def unique_name(want): data += ' + ["//tests:fixtures"]' if name == "tracedecay" and t["name"] == "memory_suite": data += ' + ["//evals/memory:scenarios"]' + if name == "tracedecay" and t["name"] == "claude_observation_benchmark": + data += ' + ["//benchmark_data:claude-observation"]' + if name == "tracedecay" and t["name"] == "session_temporal": + workload, _, _ = group_label( + Path("benchmark_data/session-temporal/workload-v1.json") + ) + data += " + [" + q([workload]) + "]" # include_str!/include_bytes! are compile-time inputs. They cover crate-root # resource dirs beside tests/ and repo-root fixtures under //tests. compile_data = ( @@ -1126,6 +1139,8 @@ def unique_name(want): "rust_test(", f' name = "{test_name}",', test_size_attr(test_name), + " use_libtest_harness = False," + if not declared_target.get("harness", True) else None, " srcs = " + srcs + ",", f' crate_root = {crate_root},', ' edition = crate_edition(),', diff --git a/scripts/run-claude-observation-benchmark.sh b/scripts/run-claude-observation-benchmark.sh index 5532409402..877c031c99 100755 --- a/scripts/run-claude-observation-benchmark.sh +++ b/scripts/run-claude-observation-benchmark.sh @@ -6,7 +6,7 @@ cd "$repo_root" git_root=$(git rev-parse --show-toplevel) if [[ $(cd "$git_root" && pwd -P) != "$repo_root" ]]; then - echo "benchmark runner must execute from the CARGO_MANIFEST_DIR Git worktree" >&2 + echo "benchmark runner must execute from the current Git worktree" >&2 exit 1 fi if [[ -n $(git status --porcelain=v1 --untracked-files=normal --ignore-submodules=none) ]]; then @@ -15,7 +15,6 @@ if [[ -n $(git status --porcelain=v1 --untracked-files=normal --ignore-submodule fi commit=$(git rev-parse HEAD) -tree=$(git rev-parse 'HEAD^{tree}') short_commit=${commit:0:8} result_name="result-$(date -u +%F)-${short_commit}.json" result_path="benchmark_data/claude-observation/$result_name" @@ -29,117 +28,20 @@ if ! grep -q '"current_acceptance": null' "$index_path"; then exit 1 fi -scratch_root="$repo_root/target/claude-observation-source" -mkdir -p "$scratch_root" -build_root=$(mktemp -d "$scratch_root/archive.XXXXXXXX") -capture=$(mktemp) -index_backup=$(mktemp) -cp "$index_path" "$index_backup" -complete=false +staging=$(mktemp -d "${TMPDIR:-/tmp}/claude-observation-bench.XXXXXXXX") cleanup() { - cd "$repo_root" - rm -f "$capture" - if [[ $complete != true ]]; then - rm -f "$result_path" - cp "$index_backup" "$index_path" - fi - rm -f "$index_backup" - if [[ -d ${build_root:-} ]]; then - chmod -R u+w "$build_root" || true - rm -rf "$build_root" - fi + rm -rf "$staging" } trap cleanup EXIT -git archive "$commit" | tar -x -C "$build_root" +capture="$staging/capture.log" -write_source_manifest() { - local destination=$1 - : >"$destination" - while IFS= read -r -d '' relative; do - mode=$(git ls-files -s -- "$relative" | awk '{print $1}') - if [[ $mode == 160000 ]]; then - digest=$(git rev-parse "$commit:$relative") - else - digest=$(sha256sum "$build_root/$relative" | awk '{print $1}') - fi - printf '%s\t%s\t%s\n' "$mode" "$digest" "$relative" >>"$destination" - done < <(git ls-files -z) -} - -source_manifest="$build_root/.tracedecay-benchmark-source-manifest" -write_source_manifest "$source_manifest" -source_manifest_sha256=$(sha256sum "$source_manifest" | awk '{print $1}') - -host_target=$(rustc -Vv | sed -n 's/^host: //p') -[[ -n $host_target ]] || { echo "could not resolve rustc host target" >&2; exit 1; } -export CARGO_BUILD_TARGET=$host_target -unset RUSTFLAGS -export CARGO_ENCODED_RUSTFLAGS= - -config_identity=$({ - for config in "$build_root/.cargo/config.toml" \ - "${CARGO_HOME:-${HOME:-}/.cargo}/config" \ - "${CARGO_HOME:-${HOME:-}/.cargo}/config.toml"; do - if [[ -f $config ]]; then - printf '%s\t%s\n' "$(basename "$config")" "$(git hash-object "$config")" - else - printf '%s\tmissing\n' "$(basename "$config")" - fi - done - env | LC_ALL=C sort | grep -E '^(AR|CC|CFLAGS|CXX|CXXFLAGS|LDFLAGS|RUSTC|RUSTUP_TOOLCHAIN|CARGO_BUILD_TARGET|CARGO_PROFILE_RELEASE_)=' || true -} | git hash-object --stdin) - -wrapper_identity() { - local wrapper=$1 - if [[ -z $wrapper ]]; then - printf 'environment:none;cargo_config:%s' "$config_identity" - return - fi - local version - version=$($wrapper --version 2>&1 | head -n 1) || version=version-unavailable - printf 'environment:%s;%s' "$(basename "$wrapper")" "$version" -} - -export TRACEDECAY_BENCHMARK_BUILD_COMMIT=$commit -export TRACEDECAY_BENCHMARK_BUILD_TREE=$tree -export TRACEDECAY_BENCHMARK_BUILD_PROFILE=release -export TRACEDECAY_BENCHMARK_BUILD_SOURCE_MODE=git_archive_read_only_v1 -export TRACEDECAY_BENCHMARK_SOURCE_MANIFEST_SHA256=$source_manifest_sha256 -export TRACEDECAY_BENCHMARK_BUILD_TARGET_TRIPLE=$host_target -export TRACEDECAY_BENCHMARK_BUILD_RUSTC_VERSION="$(rustc -Vv)" -export TRACEDECAY_BENCHMARK_BUILD_CARGO_VERSION="$(cargo -V)" -export TRACEDECAY_BENCHMARK_BUILD_RUSTFLAGS=normalized-empty -export TRACEDECAY_BENCHMARK_BUILD_RUSTC_WRAPPER="$(wrapper_identity "${RUSTC_WRAPPER:-}")" -export TRACEDECAY_BENCHMARK_BUILD_RUSTC_WORKSPACE_WRAPPER="$(wrapper_identity "${RUSTC_WORKSPACE_WRAPPER:-}")" -export TRACEDECAY_BENCHMARK_BUILD_CARGO_CONFIG_IDENTITY=$config_identity - -# A fresh Git archive intentionally contains no generated dashboard assets. -# Generate them before freezing the source tree, then prove pnpm did not mutate -# any tracked input. The measured Cargo build can remain fully read-only. -( - cd "$build_root" - pnpm install --frozen-lockfile - cd dashboard - pnpm run build -) -post_dashboard_manifest="$build_root/.tracedecay-benchmark-source-manifest.post-dashboard" -write_source_manifest "$post_dashboard_manifest" -if ! cmp -s "$source_manifest" "$post_dashboard_manifest"; then - echo "dashboard build modified tracked benchmark source" >&2 - exit 1 -fi -rm -f "$post_dashboard_manifest" - -mkdir -p "$build_root/target" -chmod -R a-w "$build_root" -chmod u+rwx "$build_root/target" - -( - cd "$build_root" - cargo test --quiet --release -p tracedecay --features test-helpers \ - --test claude_observation_benchmark production_observation_pipeline_baseline -- \ - --ignored --exact --nocapture --test-threads=1 -) 2>&1 | tee "$capture" +bazel test --config=release --config=ci //crates/tracedecay:claude_observation_benchmark \ + --test_strategy=standalone --nocache_test_results --test_output=all \ + --test_env=TRACEDECAY_BENCHMARK_REPO_ROOT="$repo_root" \ + --test_env=TRACEDECAY_BENCHMARK_BAZEL_VERSION="$(bazel --version)" \ + --test_arg=production_observation_pipeline_baseline \ + --test_arg=--ignored --test_arg=--exact --test_arg=--quiet --test_arg=--nocapture --test_arg=--test-threads=1 \ + 2>&1 | tee "$capture" if [[ $(grep -c '^TRACEDECAY_CLAUDE_OBSERVATION_BENCHMARK_RESULT=' "$capture") -ne 1 ]]; then echo "benchmark did not emit exactly one result" >&2 @@ -150,19 +52,22 @@ if [[ -z $result_json ]]; then echo "benchmark result marker was malformed" >&2 exit 1 fi -printf '%s\n' "$result_json" >"$result_path" - +# Stage only the new output. Historical evidence is read through symlinks, +# without creating a second copy of any existing artifact. +for historical in "$repo_root"/benchmark_data/claude-observation/result-*.json; do + ln -s "$historical" "$staging/$(basename "$historical")" +done +printf '%s\n' "$result_json" >"$staging/$result_name" sed "s/\"current_acceptance\": null/\"current_acceptance\": \"$result_name\"/" \ - "$index_backup" >"$index_path" -( - cd "$build_root" - TRACEDECAY_BENCHMARK_REQUIRE_ACCEPTANCE=1 \ - TRACEDECAY_BENCHMARK_EVIDENCE_DIR="$repo_root/benchmark_data/claude-observation" \ - cargo test --quiet --release -p tracedecay --features test-helpers \ - --test claude_observation_benchmark evidence_directory_matches_index_contract -- \ - --exact --test-threads=1 -) - -complete=true + "$index_path" >"$staging/evidence-index.json" +scripts/require-exact-test.sh bazel test --config=release --config=ci //crates/tracedecay:claude_observation_benchmark \ + --test_strategy=standalone --nocache_test_results --test_output=all \ + --test_env=TRACEDECAY_BENCHMARK_REPO_ROOT="$repo_root" \ + --test_env=TRACEDECAY_BENCHMARK_REQUIRE_ACCEPTANCE=1 \ + --test_env=TRACEDECAY_BENCHMARK_EVIDENCE_DIR="$staging" \ + --test_arg=evidence_directory_matches_index_contract --test_arg=--exact \ + --test_arg=--test-threads=1 + +mv "$staging/$result_name" "$result_path" +mv "$staging/evidence-index.json" "$index_path" echo "validated $result_path" -echo "commit only the result, evidence index, and README summary as the evidence follow-up" diff --git a/scripts/run-session-temporal-benchmark.sh b/scripts/run-session-temporal-benchmark.sh index d892c47231..fcad7d5f1c 100755 --- a/scripts/run-session-temporal-benchmark.sh +++ b/scripts/run-session-temporal-benchmark.sh @@ -5,12 +5,12 @@ usage() { cat <<'EOF' Usage: scripts/run-session-temporal-benchmark.sh --dry-run|--run|--refresh-contract - --dry-run Read-only, Cargo-free validation of harness artifacts and + --dry-run Read-only, build-free validation of harness artifacts and Codex fixture provenance. Does not mutate the checkout. - --run Diagnostic measurement through the optimized bench profile on + --run Diagnostic measurement through the Bazel release configuration on Linux and macOS. Isolates HOME and TRACEDECAY_DATA_DIR for the child process. - Windows CI proves temporal durability via nextest. + Windows CI proves temporal durability via Bazel. --refresh-contract Run the same real measurement from a clean source commit, then publish the result with its clean-commit provenance (Linux-hosted). @@ -37,7 +37,6 @@ validate_harness_evidence() { import json import pathlib import sys -import tomllib root = pathlib.Path(sys.argv[1]) benchmark_root = root / "benchmark_data/session-temporal" @@ -165,17 +164,6 @@ require("attestation" not in json.dumps(historical_result).lower(), require("attestation" not in json.dumps(workload).lower(), "deleted attestation terminology remains in workload") -with (root / "Cargo.toml").open("rb") as handle: - cargo = tomllib.load(handle) -profile = cargo.get("profile", {}).get("bench", {}) -require(profile == { - "opt-level": 3, - "debug": False, - "debug-assertions": False, - "overflow-checks": False, - "incremental": False, -}, "optimized bench profile mismatch") - storage = workload.get("storage_isolation") or {} require("HOME" in storage.get("required_environment", []), "HOME isolation required") require("TRACEDECAY_DATA_DIR" in storage.get("required_environment", []), @@ -194,23 +182,16 @@ run_benchmark() { exit 64 ;; *) - printf '%s\n' "Session-temporal ${mode} measurement harness supports Linux and macOS; use CI nextest durable coverage on other platforms" >&2 + printf '%s\n' "Session-temporal ${mode} measurement harness supports Linux and macOS; use CI Bazel durable coverage on other platforms" >&2 exit 64 ;; esac - isolation_root="$(mktemp -d "${TMPDIR:-/tmp}/session-temporal-bench.XXXXXX")" - cargo_home="${CARGO_HOME:-$HOME/.cargo}" - rustup_home="${RUSTUP_HOME:-$HOME/.rustup}" - cleanup() { - rm -rf "$isolation_root" - } - trap cleanup EXIT - export HOME="$isolation_root/home" - export TRACEDECAY_DATA_DIR="$isolation_root/tracedecay-data" - export CARGO_HOME="$cargo_home" - export RUSTUP_HOME="$rustup_home" - mkdir -p "$HOME" "$TRACEDECAY_DATA_DIR" - cargo bench -p tracedecay --bench session_temporal --all-features -- "$mode" + # The Bazel test launcher owns HOME and TraceDecay profile isolation. + # Local execution lets contract refresh publish into this exact checkout. + bazel test --config=release --config=ci //crates/tracedecay:session_temporal \ + --test_strategy=standalone --nocache_test_results --test_output=all \ + --test_env=TRACEDECAY_BENCHMARK_REPO_ROOT="$repo_root" \ + --test_arg=--bench --test_arg="$mode" } if [[ $# -ne 1 ]]; then @@ -224,7 +205,7 @@ cd "$repo_root" case "$1" in --dry-run) validate_harness_evidence - printf 'OK: session-temporal dry-run validated harness_ready evidence (Cargo-free, no mutation)\n' + printf 'OK: session-temporal dry-run validated harness_ready evidence (build-free, no mutation)\n' ;; --run) run_benchmark --run From 5124ba4cfc021e2f2f4766d802e7228d6c472f0d Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 15:58:30 -0700 Subject: [PATCH 19/29] fix(benchmarks): remove unpublished results after interruption --- .github/workflows/ci.yml | 3 + scripts/run-claude-observation-benchmark.sh | 32 ++- ...laude-observation-benchmark-publication.py | 185 ++++++++++++++++++ 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 scripts/test-claude-observation-benchmark-publication.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7058b08f0..5c78ccd70b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,9 @@ jobs: working-directory: benchmark_data/runtime run: python3 -m unittest discover -s tests -p 'test_*.py' + - name: Test benchmark result publication + run: python3 scripts/test-claude-observation-benchmark-publication.py + # The cheap repository gates in one job: commit lint, cache lineage, the # release guards, and rustfmt. Each used to hold its own ubuntu-latest # runner for a couple of minutes of work; on 20 free slots that is three diff --git a/scripts/run-claude-observation-benchmark.sh b/scripts/run-claude-observation-benchmark.sh index 877c031c99..3f60de70d1 100755 --- a/scripts/run-claude-observation-benchmark.sh +++ b/scripts/run-claude-observation-benchmark.sh @@ -29,10 +29,39 @@ if ! grep -q '"current_acceptance": null' "$index_path"; then fi staging=$(mktemp -d "${TMPDIR:-/tmp}/claude-observation-bench.XXXXXXXX") +result_move_started=false cleanup() { - rm -rf "$staging" + local status=$? index_status=0 + trap - EXIT INT TERM + if [[ $result_move_started == true ]]; then + # The index is the publication authority, including when a signal arrives + # after its rename but before the shell can record another state change. + python3 - "$index_path" "$result_name" <<'PY' || index_status=$? +import json +import sys + +try: + with open(sys.argv[1]) as source: + selected = json.load(source)["current_acceptance"] + if selected is not None and not isinstance(selected, str): + raise ValueError("invalid current_acceptance") +except (OSError, ValueError, KeyError, TypeError) as error: + print(f"cannot determine benchmark publication; preserving result: {error}", file=sys.stderr) + sys.exit(2) +sys.exit(1 if selected == sys.argv[2] else 0) +PY + case "$index_status" in + 0) rm -f "$result_path" || status=1 ;; + 1) ;; + *) [[ $status -ne 0 ]] || status=1 ;; + esac + fi + rm -rf "$staging" || status=1 + exit "$status" } trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM capture="$staging/capture.log" bazel test --config=release --config=ci //crates/tracedecay:claude_observation_benchmark \ @@ -68,6 +97,7 @@ scripts/require-exact-test.sh bazel test --config=release --config=ci //crates/t --test_arg=evidence_directory_matches_index_contract --test_arg=--exact \ --test_arg=--test-threads=1 +result_move_started=true mv "$staging/$result_name" "$result_path" mv "$staging/evidence-index.json" "$index_path" echo "validated $result_path" diff --git a/scripts/test-claude-observation-benchmark-publication.py b/scripts/test-claude-observation-benchmark-publication.py new file mode 100644 index 0000000000..5aa81bd8bb --- /dev/null +++ b/scripts/test-claude-observation-benchmark-publication.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +"""Exercise benchmark publication and cleanup without running measurements.""" + +from __future__ import annotations + +import json +import os +from pathlib import Path +import shutil +import subprocess +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parent.parent +RUNNER = "run-claude-observation-benchmark.sh" + + +class BenchmarkPublicationTests(unittest.TestCase): + def setUp(self) -> None: + self.scratch = tempfile.TemporaryDirectory(prefix="benchmark-publication-") + self.addCleanup(self.scratch.cleanup) + root = Path(self.scratch.name) + self.repo = root / "repo" + scripts = self.repo / "scripts" + scripts.mkdir(parents=True) + for name in (RUNNER, "require-exact-test.sh"): + shutil.copy2(ROOT / "scripts" / name, scripts / name) + self.evidence = self.repo / "benchmark_data/claude-observation" + self.evidence.mkdir(parents=True) + self.historical = self.evidence / "result-historical.json" + self.historical.write_text('{"evidence_status":"historical_stale"}\n') + self.historical_bytes = self.historical.read_bytes() + self.index = self.evidence / "evidence-index.json" + self.index.write_text(json.dumps({ + "schema_version": 1, + "current_acceptance": None, + "historical_stale": [self.historical.name], + }) + "\n") + self.index_bytes = self.index.read_bytes() + git_env = {key: value for key, value in os.environ.items() if not key.startswith("GIT_")} + git_env.update(GIT_CONFIG_GLOBAL=os.devnull, GIT_CONFIG_NOSYSTEM="1") + for args in ( + ("init", "-q"), + ("add", "."), + ("-c", "user.name=Fixture", "-c", "user.email=fixture@example.invalid", + "-c", "core.hooksPath=/dev/null", "-c", "commit.gpgsign=false", + "commit", "-qm", "fixture"), + ): + subprocess.run(["git", *args], cwd=self.repo, env=git_env, check=True) + commit = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=self.repo, env=git_env, text=True + ).strip() + self.result = self.evidence / f"result-2030-01-01-{commit[:8]}.json" + bin_dir = root / "bin" + bin_dir.mkdir() + self.tmp = root / "tmp" + self.tmp.mkdir() + self.env = dict(git_env, PATH=f"{bin_dir}{os.pathsep}{os.environ['PATH']}", + TMPDIR=str(self.tmp), REAL_MV=shutil.which("mv") or "") + self.assertTrue(self.env["REAL_MV"]) + self.write_command(bin_dir, "date", "#!/bin/sh\nprintf '2030-01-01\\n'\n") + self.write_command(bin_dir, "bazel", '''#!/usr/bin/env python3 +import json +from pathlib import Path +import sys + +if sys.argv[1:] == ["--version"]: + print("bazel fixture") +elif "--test_arg=production_observation_pipeline_baseline" in sys.argv: + print('TRACEDECAY_CLAUDE_OBSERVATION_BENCHMARK_RESULT={"measured":true} ') +elif "--test_arg=evidence_directory_matches_index_contract" in sys.argv: + prefix = "--test_env=TRACEDECAY_BENCHMARK_EVIDENCE_DIR=" + directory = Path(next(arg[len(prefix):] for arg in sys.argv if arg.startswith(prefix))) + index = json.loads((directory / "evidence-index.json").read_text()) + assert json.loads((directory / index["current_acceptance"]).read_text()) == {"measured": True} + for name in index["historical_stale"]: + assert (directory / name).is_symlink() + assert json.loads((directory / name).read_text())["evidence_status"] == "historical_stale" + print("test result: ok. 1 passed; 0 failed; 0 ignored") +else: + raise SystemExit("unexpected Bazel command") +''') + self.write_command(bin_dir, "mv", '''#!/usr/bin/env python3 +import os +from pathlib import Path +import signal +import subprocess +import sys + +index_move = Path(sys.argv[1]).name == "evidence-index.json" +mode = os.environ.get("PUBLICATION_FAULT", "") +if index_move and mode == "index_move_failure": + raise SystemExit(73) +if index_move and mode == "unreadable_index": + Path(sys.argv[2]).unlink() + raise SystemExit(74) +if index_move and mode == "malformed_index": + Path(sys.argv[2]).write_text("{invalid") + raise SystemExit(74) +result = subprocess.run([os.environ["REAL_MV"], *sys.argv[1:]], check=False) +if result.returncode: + raise SystemExit(result.returncode) +boundary = "after_index" if index_move else "after_result" +if mode == boundary: + os.kill(os.getppid(), getattr(signal, os.environ["PUBLICATION_SIGNAL"])) +''') + + @staticmethod + def write_command(directory: Path, name: str, body: str) -> None: + path = directory / name + path.write_text(body) + path.chmod(0o755) + + def run_publication(self, fault: str = "", signal: str = "SIGTERM"): + completed = subprocess.run( + ["bash", str(self.repo / "scripts" / RUNNER)], cwd=self.repo, + env=dict(self.env, PUBLICATION_FAULT=fault, PUBLICATION_SIGNAL=signal), + capture_output=True, text=True, timeout=15, + ) + self.assertEqual(self.historical.read_bytes(), self.historical_bytes) + self.assertEqual(list(self.tmp.iterdir()), [], "publication staging was retained") + return completed + + def assert_published(self) -> None: + self.assertEqual(json.loads(self.result.read_text()), {"measured": True}) + self.assertEqual(json.loads(self.index.read_text())["current_acceptance"], self.result.name) + + def test_failed_index_move_removes_new_result_and_allows_retry(self) -> None: + failed = self.run_publication("index_move_failure") + self.assertEqual(failed.returncode, 73, failed.stderr) + self.assertFalse(self.result.exists()) + self.assertEqual(self.index.read_bytes(), self.index_bytes) + retried = self.run_publication() + self.assertEqual(retried.returncode, 0, retried.stderr) + self.assert_published() + + def test_interrupt_after_result_removes_only_unpublished_output(self) -> None: + for sig, code in (("SIGINT", 130), ("SIGTERM", 143)): + with self.subTest(signal=sig): + interrupted = self.run_publication("after_result", sig) + self.assertEqual(interrupted.returncode, code, interrupted.stderr) + self.assertFalse(self.result.exists()) + self.assertEqual(self.index.read_bytes(), self.index_bytes) + retried = self.run_publication() + self.assertEqual(retried.returncode, 0, retried.stderr) + self.assert_published() + + def test_interrupt_after_index_preserves_published_result(self) -> None: + interrupted = self.run_publication("after_index", "SIGTERM") + self.assertEqual(interrupted.returncode, 143, interrupted.stderr) + self.assert_published() + + def test_int_after_index_preserves_published_result(self) -> None: + interrupted = self.run_publication("after_index", "SIGINT") + self.assertEqual(interrupted.returncode, 130, interrupted.stderr) + self.assert_published() + + def test_unreadable_index_preserves_result_and_reports_cleanup_failure(self) -> None: + failed = self.run_publication("unreadable_index") + self.assertEqual(failed.returncode, 74, failed.stderr) + self.assertEqual(json.loads(self.result.read_text()), {"measured": True}) + self.assertIn("cannot determine benchmark publication; preserving result", failed.stderr) + + def test_malformed_index_preserves_result_and_reports_cleanup_failure(self) -> None: + failed = self.run_publication("malformed_index") + self.assertEqual(failed.returncode, 74, failed.stderr) + self.assertEqual(json.loads(self.result.read_text()), {"measured": True}) + self.assertIn("cannot determine benchmark publication; preserving result", failed.stderr) + + def test_preexisting_result_is_preserved(self) -> None: + original = b'{"preexisting":true}\n' + self.result.write_bytes(original) + # Ignore this fixture artifact so the real overwrite refusal, rather + # than the earlier dirty-worktree guard, is exercised. + (self.repo / ".git/info/exclude").write_text(f"{self.result.relative_to(self.repo)}\n") + refused = self.run_publication() + self.assertNotEqual(refused.returncode, 0) + self.assertIn("refusing to overwrite", refused.stderr) + self.assertEqual(self.result.read_bytes(), original) + self.assertEqual(self.index.read_bytes(), self.index_bytes) + + +if __name__ == "__main__": + unittest.main(verbosity=2) From 362a73f95cc13f5aa09c2ef5333cf373d7a55615 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 16:10:17 -0700 Subject: [PATCH 20/29] fix(bazel): format sources without production dependency analysis --- .bazelrc | 4 -- .github/workflows/ci.yml | 10 +-- BUILD.bazel | 15 +++++ crates/tracedecay-agent-hosts/BUILD.bazel | 18 +++++ crates/tracedecay-api/BUILD.bazel | 18 +++++ crates/tracedecay-application/BUILD.bazel | 18 +++++ .../tracedecay-automation-runtime/BUILD.bazel | 18 +++++ crates/tracedecay-automation/BUILD.bazel | 18 +++++ crates/tracedecay-capture/BUILD.bazel | 18 +++++ crates/tracedecay-cli/BUILD.bazel | 18 +++++ crates/tracedecay-code-extraction/BUILD.bazel | 18 +++++ .../BUILD.bazel | 18 +++++ .../tracedecay-code-index-runtime/BUILD.bazel | 18 +++++ crates/tracedecay-code-index/BUILD.bazel | 18 +++++ crates/tracedecay-configuration/BUILD.bazel | 18 +++++ crates/tracedecay-contracts/BUILD.bazel | 18 +++++ crates/tracedecay-daemon-control/BUILD.bazel | 18 +++++ crates/tracedecay-daemon-identity/BUILD.bazel | 18 +++++ crates/tracedecay-daemon-protocol/BUILD.bazel | 18 +++++ crates/tracedecay-daemon-service/BUILD.bazel | 18 +++++ crates/tracedecay-dashboard-api/BUILD.bazel | 18 +++++ crates/tracedecay-domain/BUILD.bazel | 18 +++++ crates/tracedecay-framing/BUILD.bazel | 18 +++++ crates/tracedecay-global-db/BUILD.bazel | 18 +++++ crates/tracedecay-graph-db/BUILD.bazel | 18 +++++ crates/tracedecay-graph-query/BUILD.bazel | 18 +++++ crates/tracedecay-hooks/BUILD.bazel | 18 +++++ crates/tracedecay-host-admission/BUILD.bazel | 18 +++++ .../tracedecay-host-integration/BUILD.bazel | 18 +++++ crates/tracedecay-lcm/BUILD.bazel | 18 +++++ crates/tracedecay-lsp/BUILD.bazel | 18 +++++ crates/tracedecay-maintenance/BUILD.bazel | 18 +++++ crates/tracedecay-mcp-catalog/BUILD.bazel | 18 +++++ crates/tracedecay-mcp/BUILD.bazel | 18 +++++ crates/tracedecay-policy/BUILD.bazel | 18 +++++ crates/tracedecay-privacy/BUILD.bazel | 18 +++++ crates/tracedecay-private-fs/BUILD.bazel | 18 +++++ crates/tracedecay-project/BUILD.bazel | 18 +++++ crates/tracedecay-query/BUILD.bazel | 18 +++++ crates/tracedecay-runtime-core/BUILD.bazel | 18 +++++ .../tracedecay-rusqlite-runtime/BUILD.bazel | 18 +++++ crates/tracedecay-sdk/BUILD.bazel | 18 +++++ crates/tracedecay-search-eval/BUILD.bazel | 18 +++++ crates/tracedecay-session-memory/BUILD.bazel | 18 +++++ crates/tracedecay-session-runtime/BUILD.bazel | 18 +++++ .../BUILD.bazel | 18 +++++ crates/tracedecay-sessions/BUILD.bazel | 18 +++++ crates/tracedecay-source-edit/BUILD.bazel | 18 +++++ crates/tracedecay-store-runtime/BUILD.bazel | 18 +++++ crates/tracedecay-store/BUILD.bazel | 18 +++++ crates/tracedecay-temporal-query/BUILD.bazel | 18 +++++ crates/tracedecay-tool-catalog/BUILD.bazel | 18 +++++ crates/tracedecay/BUILD.bazel | 18 +++++ scripts/BUILD.bazel | 22 ++++++ scripts/bazel/gen_builds.py | 67 ++++++++++++++++++- sdks/codegen/BUILD.bazel | 22 ++++++ 56 files changed, 1026 insertions(+), 14 deletions(-) diff --git a/.bazelrc b/.bazelrc index 62cceb8f0c..72ec4bc111 100644 --- a/.bazelrc +++ b/.bazelrc @@ -63,10 +63,6 @@ build:clippy --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect build:clippy --output_groups=clippy_checks build:clippy --@rules_rust//rust/settings:clippy_flag=-Dwarnings -build:fmt --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect -build:fmt --output_groups=rustfmt_checks -build:fmt --@rules_rust//rust/settings:rustfmt.toml=//:rustfmt.toml - # The shipped binary: Cargo's release profile (opt-level 3, no debug # assertions). Externals return to opt-level 3 over the perf overrides below. build:release --compilation_mode=opt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c78ccd70b..13cbce9116 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -197,15 +197,7 @@ jobs: cache-key: ci-format-linux - name: Check formatting - shell: bash - run: | - set -euo pipefail - # The isolated test launchers do not expose Rust source providers. - # Explicit native labels include their private manual test binaries. - targets="$(bazel query 'kind("rust_(library|binary|test|proc_macro) rule", //...)')" - test -n "$targets" - mapfile -t rust_targets <<< "$targets" - bazel build --config=ci --config=fmt "${rust_targets[@]}" + run: bazel build --config=ci //:rustfmt_check # Manifest edits that skip a gen_builds.py rerun leave stale BUILD # files; compare the committed files against a fresh generation. diff --git a/BUILD.bazel b/BUILD.bazel index e205ae97b7..72f76d5c29 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,11 @@ load("@npm//:defs.bzl", "npm_link_all_packages") +load("@rules_rust//rust:toolchain.bzl", "current_rustfmt_toolchain") + +current_rustfmt_toolchain( + name = "rustfmt_toolchain", + tags = ["manual"], + visibility = ["//visibility:public"], +) npm_link_all_packages(name = "node_modules") @@ -29,3 +36,11 @@ config_setting( values = {"compilation_mode": "opt"}, visibility = ["//visibility:public"], ) + +# BEGIN generated by scripts/bazel/gen_builds.py +filegroup( + name = "rustfmt_check", + srcs = ["//crates/tracedecay-agent-hosts:rustfmt_check_2024", "//crates/tracedecay-api:rustfmt_check_2024", "//crates/tracedecay-application:rustfmt_check_2024", "//crates/tracedecay-automation-runtime:rustfmt_check_2024", "//crates/tracedecay-automation:rustfmt_check_2024", "//crates/tracedecay-capture:rustfmt_check_2024", "//crates/tracedecay-cli:rustfmt_check_2024", "//crates/tracedecay-code-extraction:rustfmt_check_2024", "//crates/tracedecay-code-index-retention:rustfmt_check_2024", "//crates/tracedecay-code-index-runtime:rustfmt_check_2024", "//crates/tracedecay-code-index:rustfmt_check_2024", "//crates/tracedecay-configuration:rustfmt_check_2024", "//crates/tracedecay-contracts:rustfmt_check_2024", "//crates/tracedecay-daemon-control:rustfmt_check_2024", "//crates/tracedecay-daemon-identity:rustfmt_check_2024", "//crates/tracedecay-daemon-protocol:rustfmt_check_2024", "//crates/tracedecay-daemon-service:rustfmt_check_2024", "//crates/tracedecay-dashboard-api:rustfmt_check_2024", "//crates/tracedecay-domain:rustfmt_check_2024", "//crates/tracedecay-framing:rustfmt_check_2024", "//crates/tracedecay-global-db:rustfmt_check_2024", "//crates/tracedecay-graph-db:rustfmt_check_2024", "//crates/tracedecay-graph-query:rustfmt_check_2024", "//crates/tracedecay-hooks:rustfmt_check_2024", "//crates/tracedecay-host-admission:rustfmt_check_2024", "//crates/tracedecay-host-integration:rustfmt_check_2024", "//crates/tracedecay-lcm:rustfmt_check_2024", "//crates/tracedecay-lsp:rustfmt_check_2024", "//crates/tracedecay-maintenance:rustfmt_check_2024", "//crates/tracedecay-mcp-catalog:rustfmt_check_2024", "//crates/tracedecay-mcp:rustfmt_check_2024", "//crates/tracedecay-policy:rustfmt_check_2024", "//crates/tracedecay-privacy:rustfmt_check_2024", "//crates/tracedecay-private-fs:rustfmt_check_2024", "//crates/tracedecay-project:rustfmt_check_2024", "//crates/tracedecay-query:rustfmt_check_2024", "//crates/tracedecay-runtime-core:rustfmt_check_2024", "//crates/tracedecay-rusqlite-runtime:rustfmt_check_2024", "//crates/tracedecay-sdk:rustfmt_check_2024", "//crates/tracedecay-search-eval:rustfmt_check_2024", "//crates/tracedecay-session-memory:rustfmt_check_2024", "//crates/tracedecay-session-runtime:rustfmt_check_2024", "//crates/tracedecay-session-temporal-store:rustfmt_check_2024", "//crates/tracedecay-sessions:rustfmt_check_2024", "//crates/tracedecay-source-edit:rustfmt_check_2024", "//crates/tracedecay-store-runtime:rustfmt_check_2024", "//crates/tracedecay-store:rustfmt_check_2024", "//crates/tracedecay-temporal-query:rustfmt_check_2024", "//crates/tracedecay-tool-catalog:rustfmt_check_2024", "//crates/tracedecay:rustfmt_check_2024", "//scripts:rustfmt_check_2024", "//sdks/codegen:rustfmt_check_2024"], + tags = ["manual"], +) +# END generated by scripts/bazel/gen_builds.py diff --git a/crates/tracedecay-agent-hosts/BUILD.bazel b/crates/tracedecay-agent-hosts/BUILD.bazel index 1287a149cb..cf949d3a0e 100644 --- a/crates/tracedecay-agent-hosts/BUILD.bazel +++ b/crates/tracedecay-agent-hosts/BUILD.bazel @@ -135,6 +135,24 @@ rust_test( data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(["build.rs", "src/product_version/root_manifest.rs"] + glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) + filegroup( name = "src_agents_hermes_templates", srcs = glob(["src/agents/hermes/templates/**"]), diff --git a/crates/tracedecay-api/BUILD.bazel b/crates/tracedecay-api/BUILD.bazel index c34f8206bf..0474e1e202 100644 --- a/crates/tracedecay-api/BUILD.bazel +++ b/crates/tracedecay-api/BUILD.bazel @@ -101,3 +101,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-application/BUILD.bazel b/crates/tracedecay-application/BUILD.bazel index c89e59fc36..9be3fff0c5 100644 --- a/crates/tracedecay-application/BUILD.bazel +++ b/crates/tracedecay-application/BUILD.bazel @@ -137,6 +137,24 @@ rust_test( data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["benches/**/*.rs"]) + glob(["tests/application_suite/**/*.rs", "tests/application_suite/github_stack_coordinator/**/*.rs"]) + glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["tests/application_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) + filegroup( name = "src_advisory_fixtures_provider_branch_review", srcs = glob(["src/advisory/fixtures/provider_branch_review/**"]), diff --git a/crates/tracedecay-automation-runtime/BUILD.bazel b/crates/tracedecay-automation-runtime/BUILD.bazel index 753a05c8f9..32d181d17b 100644 --- a/crates/tracedecay-automation-runtime/BUILD.bazel +++ b/crates/tracedecay-automation-runtime/BUILD.bazel @@ -107,3 +107,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-automation/BUILD.bazel b/crates/tracedecay-automation/BUILD.bazel index ba85841323..1f6dc10858 100644 --- a/crates/tracedecay-automation/BUILD.bazel +++ b/crates/tracedecay-automation/BUILD.bazel @@ -56,3 +56,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-capture/BUILD.bazel b/crates/tracedecay-capture/BUILD.bazel index e02858f6ee..031434ad39 100644 --- a/crates/tracedecay-capture/BUILD.bazel +++ b/crates/tracedecay-capture/BUILD.bazel @@ -86,3 +86,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["tests/capture_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-cli/BUILD.bazel b/crates/tracedecay-cli/BUILD.bazel index 059166a48c..658aa9605d 100644 --- a/crates/tracedecay-cli/BUILD.bazel +++ b/crates/tracedecay-cli/BUILD.bazel @@ -218,3 +218,21 @@ rust_test( rustc_env = {"CARGO_BIN_EXE_tracedecay": "$(rootpath :tracedecay__test)", "CARGO_TARGET_TMPDIR": "/tmp"}, runfiles_env = {"CARGO_BIN_EXE_tracedecay": "//crates/tracedecay-cli:tracedecay__test", "TRACEDECAY_HOST_CLI_FIXTURE": "//crates/tracedecay-cli:tracedecay-host-cli-fixture", "TRACEDECAY_TEST_BIN": "//crates/tracedecay-cli:tracedecay__test"}, ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(["build.rs"] + glob(["build-support/*.rs"]) + glob(["build-support/**/*.rs"]) + glob(["src/**/*.rs"]) + glob(["build-support/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["build-support/**/*.rs", "src/**/*.rs"]) + ["//crates/tracedecay:tests_common_sources", "//tests:support_sources"] + glob(["tests/core_cli_suite/**/*.rs"]) + glob(["build-support/**/*.rs", "tests/support/**/*.rs"]) + ["//crates/tracedecay:tests_common_sources", "//tests:support_sources"] + glob(["tests/host_journeys_suite/**/*.rs"]) + glob(["tests/support/**/*.rs"]) + ["//tests:support_sources"]).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-code-extraction/BUILD.bazel b/crates/tracedecay-code-extraction/BUILD.bazel index 2686e53cc6..0f51132fa6 100644 --- a/crates/tracedecay-code-extraction/BUILD.bazel +++ b/crates/tracedecay-code-extraction/BUILD.bazel @@ -179,3 +179,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(["build.rs"] + glob(["examples/**/*.rs"]) + glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["tests/main/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-code-index-retention/BUILD.bazel b/crates/tracedecay-code-index-retention/BUILD.bazel index ba0ec6bbda..c02f6ddecf 100644 --- a/crates/tracedecay-code-index-retention/BUILD.bazel +++ b/crates/tracedecay-code-index-retention/BUILD.bazel @@ -107,3 +107,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-code-index-runtime/BUILD.bazel b/crates/tracedecay-code-index-runtime/BUILD.bazel index 16b31c2d1d..6fa5fad3d1 100644 --- a/crates/tracedecay-code-index-runtime/BUILD.bazel +++ b/crates/tracedecay-code-index-runtime/BUILD.bazel @@ -90,3 +90,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-code-index/BUILD.bazel b/crates/tracedecay-code-index/BUILD.bazel index 55001f821f..1b7b317943 100644 --- a/crates/tracedecay-code-index/BUILD.bazel +++ b/crates/tracedecay-code-index/BUILD.bazel @@ -283,3 +283,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["benches/**/*.rs"]) + glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["tests/code_index_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-configuration/BUILD.bazel b/crates/tracedecay-configuration/BUILD.bazel index 081af45c72..fd0623173f 100644 --- a/crates/tracedecay-configuration/BUILD.bazel +++ b/crates/tracedecay-configuration/BUILD.bazel @@ -107,3 +107,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-contracts/BUILD.bazel b/crates/tracedecay-contracts/BUILD.bazel index bfe8b3921e..528685348f 100644 --- a/crates/tracedecay-contracts/BUILD.bazel +++ b/crates/tracedecay-contracts/BUILD.bazel @@ -71,3 +71,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/contracts_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-daemon-control/BUILD.bazel b/crates/tracedecay-daemon-control/BUILD.bazel index e443380f6a..dc60cc3d0c 100644 --- a/crates/tracedecay-daemon-control/BUILD.bazel +++ b/crates/tracedecay-daemon-control/BUILD.bazel @@ -56,3 +56,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-daemon-identity/BUILD.bazel b/crates/tracedecay-daemon-identity/BUILD.bazel index 214ef9963f..e7e4e107e3 100644 --- a/crates/tracedecay-daemon-identity/BUILD.bazel +++ b/crates/tracedecay-daemon-identity/BUILD.bazel @@ -56,3 +56,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-daemon-protocol/BUILD.bazel b/crates/tracedecay-daemon-protocol/BUILD.bazel index 0ca78c8491..9d51e6b21c 100644 --- a/crates/tracedecay-daemon-protocol/BUILD.bazel +++ b/crates/tracedecay-daemon-protocol/BUILD.bazel @@ -56,3 +56,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-daemon-service/BUILD.bazel b/crates/tracedecay-daemon-service/BUILD.bazel index ca318b7f52..dcc239eba0 100644 --- a/crates/tracedecay-daemon-service/BUILD.bazel +++ b/crates/tracedecay-daemon-service/BUILD.bazel @@ -121,3 +121,21 @@ rust_test( + ["//crates/tracedecay-hooks:fixtures_host_events_kimi"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-dashboard-api/BUILD.bazel b/crates/tracedecay-dashboard-api/BUILD.bazel index a9501693ed..179fad95d0 100644 --- a/crates/tracedecay-dashboard-api/BUILD.bazel +++ b/crates/tracedecay-dashboard-api/BUILD.bazel @@ -90,3 +90,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-domain/BUILD.bazel b/crates/tracedecay-domain/BUILD.bazel index 9a01d96a6c..a48ee57632 100644 --- a/crates/tracedecay-domain/BUILD.bazel +++ b/crates/tracedecay-domain/BUILD.bazel @@ -102,6 +102,24 @@ rust_test( data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["tests/domain_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) + filegroup( name = "tests", srcs = glob(["tests/**"]), diff --git a/crates/tracedecay-framing/BUILD.bazel b/crates/tracedecay-framing/BUILD.bazel index 194cff778d..b70290465d 100644 --- a/crates/tracedecay-framing/BUILD.bazel +++ b/crates/tracedecay-framing/BUILD.bazel @@ -56,3 +56,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-global-db/BUILD.bazel b/crates/tracedecay-global-db/BUILD.bazel index 1ef35f66d5..711c005706 100644 --- a/crates/tracedecay-global-db/BUILD.bazel +++ b/crates/tracedecay-global-db/BUILD.bazel @@ -185,3 +185,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["benches/**/*.rs"]) + glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-graph-db/BUILD.bazel b/crates/tracedecay-graph-db/BUILD.bazel index 8e1b496f21..f462978ce0 100644 --- a/crates/tracedecay-graph-db/BUILD.bazel +++ b/crates/tracedecay-graph-db/BUILD.bazel @@ -258,3 +258,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["benches/**/*.rs"]) + glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["tests/graph_db_suite/**/*.rs"]) + glob(["tests/support/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-graph-query/BUILD.bazel b/crates/tracedecay-graph-query/BUILD.bazel index f96e871b8c..53b25bcb0f 100644 --- a/crates/tracedecay-graph-query/BUILD.bazel +++ b/crates/tracedecay-graph-query/BUILD.bazel @@ -107,3 +107,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-hooks/BUILD.bazel b/crates/tracedecay-hooks/BUILD.bazel index 78886b4b43..8271233b8b 100644 --- a/crates/tracedecay-hooks/BUILD.bazel +++ b/crates/tracedecay-hooks/BUILD.bazel @@ -72,6 +72,24 @@ rust_test( data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) + filegroup( name = "fixtures_host_events", srcs = glob(["fixtures/host_events/**"]), diff --git a/crates/tracedecay-host-admission/BUILD.bazel b/crates/tracedecay-host-admission/BUILD.bazel index f5acf584be..7311301fbc 100644 --- a/crates/tracedecay-host-admission/BUILD.bazel +++ b/crates/tracedecay-host-admission/BUILD.bazel @@ -122,3 +122,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-host-integration/BUILD.bazel b/crates/tracedecay-host-integration/BUILD.bazel index fb8bb1fc53..ef2076b7ab 100644 --- a/crates/tracedecay-host-integration/BUILD.bazel +++ b/crates/tracedecay-host-integration/BUILD.bazel @@ -56,3 +56,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-lcm/BUILD.bazel b/crates/tracedecay-lcm/BUILD.bazel index 26963a865b..8c8a1317c1 100644 --- a/crates/tracedecay-lcm/BUILD.bazel +++ b/crates/tracedecay-lcm/BUILD.bazel @@ -88,3 +88,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/lcm_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-lsp/BUILD.bazel b/crates/tracedecay-lsp/BUILD.bazel index 80c12b114b..d4d038f332 100644 --- a/crates/tracedecay-lsp/BUILD.bazel +++ b/crates/tracedecay-lsp/BUILD.bazel @@ -88,3 +88,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/lsp_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-maintenance/BUILD.bazel b/crates/tracedecay-maintenance/BUILD.bazel index 970b4dca5e..11a2698adf 100644 --- a/crates/tracedecay-maintenance/BUILD.bazel +++ b/crates/tracedecay-maintenance/BUILD.bazel @@ -90,3 +90,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-mcp-catalog/BUILD.bazel b/crates/tracedecay-mcp-catalog/BUILD.bazel index a9c185e56a..b7edf16eef 100644 --- a/crates/tracedecay-mcp-catalog/BUILD.bazel +++ b/crates/tracedecay-mcp-catalog/BUILD.bazel @@ -56,3 +56,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-mcp/BUILD.bazel b/crates/tracedecay-mcp/BUILD.bazel index 124afe31f9..c9a899cf69 100644 --- a/crates/tracedecay-mcp/BUILD.bazel +++ b/crates/tracedecay-mcp/BUILD.bazel @@ -163,3 +163,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-policy/BUILD.bazel b/crates/tracedecay-policy/BUILD.bazel index b44c42af12..c29eff46f0 100644 --- a/crates/tracedecay-policy/BUILD.bazel +++ b/crates/tracedecay-policy/BUILD.bazel @@ -71,3 +71,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/policy_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-privacy/BUILD.bazel b/crates/tracedecay-privacy/BUILD.bazel index 8de389d2f3..b90efbdaf3 100644 --- a/crates/tracedecay-privacy/BUILD.bazel +++ b/crates/tracedecay-privacy/BUILD.bazel @@ -71,3 +71,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-private-fs/BUILD.bazel b/crates/tracedecay-private-fs/BUILD.bazel index be3b4e19d4..2083784af0 100644 --- a/crates/tracedecay-private-fs/BUILD.bazel +++ b/crates/tracedecay-private-fs/BUILD.bazel @@ -88,3 +88,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-project/BUILD.bazel b/crates/tracedecay-project/BUILD.bazel index 219868b08e..0f98722a1f 100644 --- a/crates/tracedecay-project/BUILD.bazel +++ b/crates/tracedecay-project/BUILD.bazel @@ -90,3 +90,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-query/BUILD.bazel b/crates/tracedecay-query/BUILD.bazel index c0b084719d..bc7860ad73 100644 --- a/crates/tracedecay-query/BUILD.bazel +++ b/crates/tracedecay-query/BUILD.bazel @@ -285,3 +285,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["benches/**/*.rs"]) + glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["tests/search_quality_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-runtime-core/BUILD.bazel b/crates/tracedecay-runtime-core/BUILD.bazel index b4936b86e5..05133d10e0 100644 --- a/crates/tracedecay-runtime-core/BUILD.bazel +++ b/crates/tracedecay-runtime-core/BUILD.bazel @@ -180,3 +180,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["benches/**/*.rs"]) + glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + glob(["tests/runtime_core_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-rusqlite-runtime/BUILD.bazel b/crates/tracedecay-rusqlite-runtime/BUILD.bazel index 1247a21353..8150ded139 100644 --- a/crates/tracedecay-rusqlite-runtime/BUILD.bazel +++ b/crates/tracedecay-rusqlite-runtime/BUILD.bazel @@ -86,3 +86,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + ["//tests:storage_runtime_rusqlite_suite_sources"] + glob(["tests/rusqlite_suite/**/*.rs"]) + glob(["tests/support/**/*.rs"]) + ["//tests:storage_runtime_rusqlite_suite_sources"]).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-sdk/BUILD.bazel b/crates/tracedecay-sdk/BUILD.bazel index a3508777e1..13bd736257 100644 --- a/crates/tracedecay-sdk/BUILD.bazel +++ b/crates/tracedecay-sdk/BUILD.bazel @@ -92,3 +92,21 @@ rust_test( exports_files(["src/bin/generate.rs", "src/codegen.rs", "src/operations.rs"], visibility = ["//visibility:public"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + ["//tests:support_sources"] + glob(["tests/sdk_suite/**/*.rs"]) + ["//tests:support_sources"]).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-search-eval/BUILD.bazel b/crates/tracedecay-search-eval/BUILD.bazel index 8afb9c7258..6ab3b9bcad 100644 --- a/crates/tracedecay-search-eval/BUILD.bazel +++ b/crates/tracedecay-search-eval/BUILD.bazel @@ -148,3 +148,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-session-memory/BUILD.bazel b/crates/tracedecay-session-memory/BUILD.bazel index 23a4845719..a3c6c5b1cc 100644 --- a/crates/tracedecay-session-memory/BUILD.bazel +++ b/crates/tracedecay-session-memory/BUILD.bazel @@ -108,6 +108,24 @@ rust_test( data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) + filegroup( name = "src", srcs = glob(["src/**"]), diff --git a/crates/tracedecay-session-runtime/BUILD.bazel b/crates/tracedecay-session-runtime/BUILD.bazel index c3a38fb0af..ef5b2164c5 100644 --- a/crates/tracedecay-session-runtime/BUILD.bazel +++ b/crates/tracedecay-session-runtime/BUILD.bazel @@ -240,3 +240,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-session-temporal-store/BUILD.bazel b/crates/tracedecay-session-temporal-store/BUILD.bazel index b78c6005d1..35d5ada7a9 100644 --- a/crates/tracedecay-session-temporal-store/BUILD.bazel +++ b/crates/tracedecay-session-temporal-store/BUILD.bazel @@ -107,3 +107,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-sessions/BUILD.bazel b/crates/tracedecay-sessions/BUILD.bazel index d07fe70a63..238c1fba10 100644 --- a/crates/tracedecay-sessions/BUILD.bazel +++ b/crates/tracedecay-sessions/BUILD.bazel @@ -105,3 +105,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["benches/**/*.rs"]) + glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-source-edit/BUILD.bazel b/crates/tracedecay-source-edit/BUILD.bazel index 53ea2ef374..837023281e 100644 --- a/crates/tracedecay-source-edit/BUILD.bazel +++ b/crates/tracedecay-source-edit/BUILD.bazel @@ -90,3 +90,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-store-runtime/BUILD.bazel b/crates/tracedecay-store-runtime/BUILD.bazel index 69e879ec25..985d3dae86 100644 --- a/crates/tracedecay-store-runtime/BUILD.bazel +++ b/crates/tracedecay-store-runtime/BUILD.bazel @@ -90,3 +90,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-store/BUILD.bazel b/crates/tracedecay-store/BUILD.bazel index 0dc65a5276..f33ca1c23d 100644 --- a/crates/tracedecay-store/BUILD.bazel +++ b/crates/tracedecay-store/BUILD.bazel @@ -71,3 +71,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["test-support/**/*.rs"]) + glob(["tests/store_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-temporal-query/BUILD.bazel b/crates/tracedecay-temporal-query/BUILD.bazel index a5dbddd276..530d249a24 100644 --- a/crates/tracedecay-temporal-query/BUILD.bazel +++ b/crates/tracedecay-temporal-query/BUILD.bazel @@ -56,3 +56,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay-tool-catalog/BUILD.bazel b/crates/tracedecay-tool-catalog/BUILD.bazel index 10f53b9c6e..e87f356ac6 100644 --- a/crates/tracedecay-tool-catalog/BUILD.bazel +++ b/crates/tracedecay-tool-catalog/BUILD.bazel @@ -71,3 +71,21 @@ rust_test( + ["//tests:fixtures"], data = glob(["tests/**","assets/**","fixtures/**","data/**","resources/**","vendor/**"], allow_empty = True) + ["//tests:fixtures"], ) + +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(glob(["src/**/*.rs"]) + glob(["tests/tool_catalog_suite/**/*.rs"])).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) diff --git a/crates/tracedecay/BUILD.bazel b/crates/tracedecay/BUILD.bazel index 53b2ac3a31..4b063d7ec9 100644 --- a/crates/tracedecay/BUILD.bazel +++ b/crates/tracedecay/BUILD.bazel @@ -490,6 +490,24 @@ rust_test( runfiles_env = {"TRACEDECAY_TEST_BIN": "//crates/tracedecay-cli:tracedecay__test"}, ) +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(["//tests:storage_runtime_rusqlite_suite_sources"] + glob(["benches/**/*.rs"]) + glob(["examples/**/*.rs"]) + glob(["src/**/*.rs"]) + glob(["benches/rmcp/**/*.rs", "benches/session_temporal/**/*.rs", "tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/**/*.rs"], exclude = ["tests/fixtures/**"]) + ["//crates/tracedecay-domain:tests_domain_suite_work_product_contract_sources", "//crates/tracedecay-domain:tests_sources", "//tests:support_sources"] + glob(["tests/agent_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/automation_runner_test/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/claude_observation_benchmark/**/*.rs"]) + glob(["tests/daemon_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/dashboard_api_test/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/graph_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/hermes_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/hooks_lsp_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/mcp_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/memory_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/product_surface_suite/**/*.rs"]) + glob(["tests/runtime_acceptance_suite/**/*.rs"]) + glob(["tests/common/**/*.rs", "tests/session_suite/**/*.rs", "tests/session_suite/temporal_projection/**/*.rs", "tests/storage_suite/**/*.rs"]) + ["//crates/tracedecay-domain:tests_domain_suite_work_product_contract_sources", "//crates/tracedecay-domain:tests_sources", "//tests:support_sources"] + glob(["tests/session_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/storage_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/transcript_ingest_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"] + glob(["tests/transport_acceptance_suite/**/*.rs"]) + glob(["tests/common/**/*.rs"]) + ["//tests:support_sources"]).to_list(), + tags = ["manual"], +) + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) + filegroup( name = "tests_common", srcs = glob(["tests/common/**"]), diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel index 93ab68dfb3..db3d4721cc 100644 --- a/scripts/BUILD.bazel +++ b/scripts/BUILD.bazel @@ -19,3 +19,25 @@ rust_test( }, use_libtest_harness = False, ) + +# BEGIN generated by scripts/bazel/gen_builds.py +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(["bazel/rust_test_environment_probe.rs"]).to_list(), + tags = ["manual"], +) + + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) + +# END generated by scripts/bazel/gen_builds.py diff --git a/scripts/bazel/gen_builds.py b/scripts/bazel/gen_builds.py index c4a2e37ebe..5dab619564 100755 --- a/scripts/bazel/gen_builds.py +++ b/scripts/bazel/gen_builds.py @@ -800,9 +800,52 @@ def needs_env_tmpdir(p, t, extra_dirs=()): for _, text in target_sources(t, tuple(extra_dirs)) ) + formatting_targets = [] + + def formatting_rules(package, sources_by_edition): + rules = [] + for edition, expressions in sorted(sources_by_edition.items()): + sources = f"rustfmt_sources_{edition}" + check = f"rustfmt_check_{edition}" + formatting_targets.append(f"//{package}:{check}") + args = ( + '--config-path "$(location //:rustfmt.toml)" ' + f'--edition {edition} --config skip_children=true --check' + ) + command = f'"$(RUSTFMT)" {args} $(locations :{sources}) && touch "$@"' + # PowerShell executes the generated command as a script. Invoke + # the formatter per file there to stay below Windows' argv limit. + powershell = ( + f"foreach ($$source in ('$(locations :{sources})' -split ' ')) {{ " + f'& "$(RUSTFMT)" {args} $$source; ' + 'if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; ' + 'New-Item -ItemType File -Path "$@" -Force | Out-Null' + ) + rules.extend([ + 'filegroup(\n' + f' name = "{sources}",\n' + ' srcs = depset(' + " + ".join(sorted(expressions)) + ').to_list(),\n' + ' tags = ["manual"],\n)\n', + 'genrule(\n' + f' name = "{check}",\n' + f' srcs = [":{sources}", "//:rustfmt.toml"],\n' + f' outs = ["{check}.ok"],\n' + f' cmd = {json.dumps(command)},\n' + f' cmd_ps = {json.dumps(powershell)},\n' + ' tools = ["//:rustfmt_toolchain"],\n' + ' toolchains = ["//:rustfmt_toolchain"],\n' + ' tags = ["manual"],\n' + ' visibility = ["//visibility:public"],\n)\n', + ]) + return rules + def render_build(p): name = p["name"] manifest = tomllib.loads(Path(p["manifest_path"]).read_text()) + formatting_sources = {} + + def format_sources(expression, edition): + formatting_sources.setdefault(edition, set()).add(expression) test_sizes = (p.get("metadata") or {}).get("bazel", {}).get("test_sizes", {}) if not isinstance(test_sizes, dict) or any( size not in ("small", "medium", "large", "enormous") @@ -882,6 +925,7 @@ def unique_name(want): srcs_expr_lib += " + glob([" + q(lib_extra) + "])" if lib_mod_labels: srcs_expr_lib += " + [" + q(lib_mod_labels) + "]" + format_sources(srcs_expr_lib, lib_t["edition"]) out += [ "rust_library(", f' name = "{tgt}",', @@ -909,6 +953,7 @@ def unique_name(want): if has_build: srcs = srcs_expr(["build.rs"] + BUILD_SCRIPT_SRCS_EXTRA.get(name, [])) + format_sources(srcs, p["edition"]) build_edges = dedup( label(d, build_tree) for d in md["build"] if d in build_tree ) @@ -955,6 +1000,7 @@ def unique_name(want): srcs += " + glob([" + q(bin_local) + "])" if bin_labels: srcs += " + [" + q(bin_labels) + "]" + format_sources(srcs, t["edition"]) req = frozenset(t.get("required-features") or []) # Cargo builds examples against the dev resolution, and builds the # binaries a test run spawns with that run's features too (test @@ -1085,6 +1131,7 @@ def unique_name(want): srcs += " + glob([" + q(mod_local) + "])" if mod_labels: srcs += " + [" + q(mod_labels) + "]" + format_sources(srcs, t["edition"]) runtime_globs = dedup([ f"{Path(rel_src(p, t)).parent}/**", *[f"{directory}/**" for directory in RESOURCE_DIRS if directory != "src"], @@ -1209,6 +1256,7 @@ def unique_name(want): ' visibility = ["//visibility:public"],', ')\n', ] + out.extend(formatting_rules(dir_of[name], formatting_sources)) return "\n".join(out) outputs = {} @@ -1340,10 +1388,27 @@ def render_shared(path, pkg_dir): obsolete.append(path) else: outputs[path] = rendered + # Handwritten Rust targets outside Cargo members use the same source-only + # checks. SDK generator sources are already covered by tracedecay-sdk. + for package, sources in { + "scripts": {"2024": {'["bazel/rust_test_environment_probe.rs"]'}}, + "sdks/codegen": {"2024": {'["dashboard_schema.rs"]'}}, + }.items(): + path = REPO / package / "BUILD.bazel" + rules = formatting_rules(package, sources) + block = GEN_BEGIN + "\n" + "\n\n".join(rules) + "\n" + GEN_END + outputs[path] = splice_generated(path.read_text(), block, path) + root_build = REPO / "BUILD.bazel" + format_aggregate = ( + GEN_BEGIN + '\nfilegroup(\n name = "rustfmt_check",\n' + + " srcs = [" + q(sorted(formatting_targets)) + "],\n" + + ' tags = ["manual"],\n)\n' + GEN_END + ) + outputs[root_build] = splice_generated(root_build.read_text(), format_aggregate, root_build) # A generated block or fully generated file can also go stale when its # last Cargo reference disappears; re-render the marked ones too. for path in sorted(REPO.glob("*/BUILD.bazel")): - if path in outputs or not path.exists(): + if path.resolve() in outputs or not path.exists(): continue text = path.read_text() if ( diff --git a/sdks/codegen/BUILD.bazel b/sdks/codegen/BUILD.bazel index 80dae49629..e201407e05 100644 --- a/sdks/codegen/BUILD.bazel +++ b/sdks/codegen/BUILD.bazel @@ -37,3 +37,25 @@ rust_binary( edition = "2024", deps = ["//crates/tracedecay-dashboard-api:tracedecay-dashboard-api"], ) + +# BEGIN generated by scripts/bazel/gen_builds.py +filegroup( + name = "rustfmt_sources_2024", + srcs = depset(["dashboard_schema.rs"]).to_list(), + tags = ["manual"], +) + + +genrule( + name = "rustfmt_check_2024", + srcs = [":rustfmt_sources_2024", "//:rustfmt.toml"], + outs = ["rustfmt_check_2024.ok"], + cmd = "\"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $(locations :rustfmt_sources_2024) && touch \"$@\"", + cmd_ps = "foreach ($$source in ('$(locations :rustfmt_sources_2024)' -split ' ')) { & \"$(RUSTFMT)\" --config-path \"$(location //:rustfmt.toml)\" --edition 2024 --config skip_children=true --check $$source; if ($$LASTEXITCODE -ne 0) { exit $$LASTEXITCODE } }; New-Item -ItemType File -Path \"$@\" -Force | Out-Null", + tools = ["//:rustfmt_toolchain"], + toolchains = ["//:rustfmt_toolchain"], + tags = ["manual"], + visibility = ["//visibility:public"], +) + +# END generated by scripts/bazel/gen_builds.py From c9f72134dfcaf90a74419dd1440c53789dfa97f6 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 17:09:25 -0700 Subject: [PATCH 21/29] fix(dashboard): bound transcript pages before context hydration --- .../src/handlers/dashboard_lcm.rs | 13 +-- .../tracedecay-temporal-query/src/context.rs | 4 +- .../tests/dashboard_api_test/api.rs | 110 ++++++++++++++++++ dashboard/src/data/query/envelope.ts | 3 +- dashboard/src/ui/ReadSection.dom.test.tsx | 57 +++++++++ dashboard/src/ui/ReadSection.tsx | 18 ++- 6 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 dashboard/src/ui/ReadSection.dom.test.tsx diff --git a/crates/tracedecay-mcp/src/handlers/dashboard_lcm.rs b/crates/tracedecay-mcp/src/handlers/dashboard_lcm.rs index f64d818932..61159cec75 100644 --- a/crates/tracedecay-mcp/src/handlers/dashboard_lcm.rs +++ b/crates/tracedecay-mcp/src/handlers/dashboard_lcm.rs @@ -11,7 +11,7 @@ use tracedecay_domain::{ use tracedecay_session_memory::session::{ SessionCursorRequest, SessionRetrievalScope, SessionTemporalQuery, }; -use tracedecay_temporal_query::context::ContextBudget; +use tracedecay_temporal_query::context::{ContextBudget, MAX_CONTEXT_RECORDS}; use tracedecay_temporal_query::ranking::DiversityLimits; use tracedecay_tool_catalog::{CapabilityId, UseCaseId}; @@ -33,7 +33,6 @@ use tracedecay_session_runtime::session_retrieval::{ const SUMMARY_DESCRIBE_CONCURRENCY: usize = 8; const DASHBOARD_AGGREGATE_PAGE_LIMIT: usize = 16; -const ADMITTED_RETRIEVAL_PAGE_LIMIT: i64 = 100; const ADMITTED_RETRIEVAL_BYTE_LIMIT: usize = 64 * 1024; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -923,8 +922,8 @@ fn retrieval_query( ) } }; - let limit = usize::try_from(limit.clamp(1, ADMITTED_RETRIEVAL_PAGE_LIMIT)).ok()?; - // The admitted application port caps each request at 100 records. Larger + let limit = usize::try_from(limit.max(1)).ok()?.min(MAX_CONTEXT_RECORDS); + // Selection must fit the canonical context assembler before hydration. Larger // dashboard windows advance only through its opaque cursor, preserving the // same immutable authorization and frozen participant manifest per page. let execution_limits = admitted_execution_limits(limit); @@ -1124,7 +1123,7 @@ mod tests { } #[test] - fn dashboard_session_page_preserves_temporal_cursor_and_exact_limit() { + fn dashboard_session_page_preserves_cursor_with_context_bounded_selection() { let request = DashboardLcmReadRequestV1::Session { session_id: "session.dashboard.cursor".to_owned(), limit: 100, @@ -1133,7 +1132,7 @@ mod tests { let query = retrieval_query(&request, initial_cursor(&request)) .expect("cursor-backed dashboard page"); - assert_eq!(query.limit(), 100); + assert_eq!(query.limit(), MAX_CONTEXT_RECORDS); assert_eq!(query.cursor(), Some("opaque-temporal-cursor")); assert!(query.semantic_filter().include_summaries); assert_eq!( @@ -1153,7 +1152,7 @@ mod tests { let query = retrieval_query(&request, Some("opaque-frozen-manifest-cursor".to_owned())) .expect("aggregate continuation"); - assert_eq!(query.limit(), 100); + assert_eq!(query.limit(), MAX_CONTEXT_RECORDS); assert_eq!(query.cursor(), Some("opaque-frozen-manifest-cursor")); assert_eq!( query.retrieval_scope(), diff --git a/crates/tracedecay-temporal-query/src/context.rs b/crates/tracedecay-temporal-query/src/context.rs index 26f2685e44..ecbe830484 100644 --- a/crates/tracedecay-temporal-query/src/context.rs +++ b/crates/tracedecay-temporal-query/src/context.rs @@ -16,7 +16,9 @@ use super::resolution::summary::SummaryOmission; use crate::execution::TemporalPortError; const CANONICAL_CONTEXT_FORMAT: &str = "tracedecay.compact_context.v1"; -const MAX_CONTEXT_RECORDS: usize = 64; +/// Maximum hydrated records accepted by one compact context assembly. +/// Paged consumers must bound selection before hydration and retain continuation. +pub const MAX_CONTEXT_RECORDS: usize = 64; const MAX_CONTEXT_ANCHORS: usize = 256; const MAX_CONTEXT_FRAME_ITEMS: usize = 256; const MAX_CONTEXT_OUTPUT_BYTES: u64 = 1024 * 1024; diff --git a/crates/tracedecay/tests/dashboard_api_test/api.rs b/crates/tracedecay/tests/dashboard_api_test/api.rs index 1ebd1477c2..88313d13ec 100644 --- a/crates/tracedecay/tests/dashboard_api_test/api.rs +++ b/crates/tracedecay/tests/dashboard_api_test/api.rs @@ -905,3 +905,113 @@ fn lcm_project_store_wins_over_global_accounting_override() { server.stop(); }); } + +#[test] +fn lcm_large_session_pages_preserve_continuation_and_timeline_counts() { + let runtime = create_runtime(); + runtime.block_on(async { + let fixture = start_dashboard_fixture_without_memory().await; + let session_id = "session-dashboard-paged"; + let session = SessionRecord { + provider: "cursor".to_owned(), + session_id: session_id.to_owned(), + project_key: fixture.host_runtime.project_id().as_str().to_owned(), + project_path: fixture.project_root.display().to_string(), + title: Some("Paged dashboard session".to_owned()), + started_at: Some(1_700_001_000), + ended_at: None, + transcript_path: None, + metadata_json: None, + parent_session_id: None, + is_subagent: false, + agent_id: None, + parent_tool_use_id: None, + }; + assert!( + fixture + .host_runtime + .upsert_session_for_test(HostAdmissionScope::Project, &session) + .await + .unwrap() + ); + for ordinal in 1..=70_u64 { + fixture + .host_runtime + .seed_session_message_observation_for_test( + tracedecay::dashboard::observation_seed::DashboardSessionMessageSeedV1 { + project_id: fixture.host_runtime.project_id().as_str(), + provider: "cursor", + session_id, + message_id: &format!("paged-message-{ordinal}"), + role: "user", + content: "A small canonical message.", + model: None, + timestamp: 1_700_001_000 + ordinal as i64, + ordinal, + }, + ) + .await + .unwrap(); + } + fixture + .host_runtime + .materialize_session_temporal_refresh_for_test(session_id) + .await + .unwrap(); + let agent = http_agent(); + let base = format!( + "{}/api/plugins/hermes-lcm/session/{session_id}?limit=100", + fixture.base_url + ); + let (status, first) = get_json(&agent, &base); + assert_eq!(status, 200); + let first_messages = first["payload"]["messages"] + .as_array() + .unwrap_or_else(|| panic!("large session must serve a bounded page: {first}")); + assert_eq!(first_messages.len(), 64); + let cursor = first["payload"]["next_cursor"] + .as_str() + .expect("bounded page continuation"); + let second_url = url::Url::parse_with_params(&base, &[("cursor", cursor)]).unwrap(); + let (status, second) = get_json(&agent, second_url.as_str()); + assert_eq!(status, 200); + let second_messages = second["payload"]["messages"] + .as_array() + .unwrap_or_else(|| panic!("continuation must serve remaining messages: {second}")); + assert_eq!(second_messages.len(), 6); + assert!(second["payload"]["next_cursor"].is_null()); + let ids = first_messages + .iter() + .chain(second_messages) + .map(|message| message["message_id"].as_str().unwrap()) + .collect::>(); + assert_eq!( + ids.len(), + 70, + "continuation must neither skip nor repeat messages" + ); + let (status, timeline) = get_json( + &agent, + &format!( + "{}/api/plugins/hermes-lcm/timeline?bucket=day&limit=400", + fixture.base_url, + ), + ); + assert_eq!(status, 200); + assert!( + timeline["payload"].is_object(), + "timeline must drain bounded pages: {timeline}" + ); + assert_eq!(timeline["coverage"]["examined"], 70, "{timeline}"); + let counted: u64 = timeline["payload"]["buckets"] + .as_array() + .unwrap() + .iter() + .map(|bucket| bucket["count"].as_u64().unwrap()) + .sum(); + assert_eq!( + counted, 70, + "timeline buckets must include both canonical pages" + ); + }); +} diff --git a/dashboard/src/data/query/envelope.ts b/dashboard/src/data/query/envelope.ts index 0eaf93c35f..8aa6f31b88 100644 --- a/dashboard/src/data/query/envelope.ts +++ b/dashboard/src/data/query/envelope.ts @@ -7,7 +7,7 @@ import { readOnlyScopeRefusal } from '../scope/store.ts'; * states rather than exceptions: the UI always has a state to render. */ export type EnvelopeResult = | { outcome: 'envelope'; envelope: DashboardEnvelopeV1 } - | { outcome: 'transport'; state: DashboardDomainStateV1; detail?: string }; + | { outcome: 'transport'; state: DashboardDomainStateV1; detail?: string; responseReceived?: true }; /** Fetches and decodes a DashboardEnvelopeV1 from a daemon API route. * - network failure → offline @@ -81,6 +81,7 @@ function decodeEnvelopeBody( outcome: 'transport', state: envelope.domain_state, detail: envelope.coverage.omission_reasons[0], + responseReceived: true, }; } return { outcome: 'envelope', envelope: envelope as DashboardEnvelopeV1 }; diff --git a/dashboard/src/ui/ReadSection.dom.test.tsx b/dashboard/src/ui/ReadSection.dom.test.tsx new file mode 100644 index 0000000000..1c17149d17 --- /dev/null +++ b/dashboard/src/ui/ReadSection.dom.test.tsx @@ -0,0 +1,57 @@ +import { cleanup, render, screen } from '@testing-library/react'; +import { afterEach, expect, it, vi } from 'vitest'; +import { z } from 'zod'; + +import { fetchEnvelope } from '../data/query/envelope.ts'; +import { fixtureEnvelope } from '../test/fixtureEnvelope.ts'; +import { envelopeReadState, ReadSection } from './ReadSection.tsx'; + +afterEach(() => { + cleanup(); + vi.unstubAllGlobals(); +}); + +it.each(['lcm_temporal_budget_execution_work_exhausted', undefined])( + 'renders a received unknown response with reason %s without claiming lost connectivity', async (reason) => { + const envelope = fixtureEnvelope(null, 'unknown'); + envelope['coverage'] = { + ...(envelope['coverage'] as Record), + omission_reasons: reason ? [reason] : [], + }; + vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify(envelope)))); + const result = await fetchEnvelope('/api/sessions/timeline', z.unknown()); + const child = vi.fn(() =>

Timeline records
); + render( + + {child} + , + ); + + expect(screen.getByText('Unknown')).toBeTruthy(); + expect(screen.getByText(`· ${reason ?? 'no usable result was provided'}`)).toBeTruthy(); + expect(screen.getByText(/The daemon responded without a usable result/)).toBeTruthy(); + expect(screen.queryByText(/No response has been recorded/)).toBeNull(); + expect(screen.queryByText(/Refresh once the daemon is serving/)).toBeNull(); + expect(screen.queryByText(/daemon unreachable/)).toBeNull(); + expect(child).not.toHaveBeenCalled(); + }, +); + +it('keeps an unobserved read distinct from a received unknown response', () => { + render( + + {() =>
Timeline records
} +
, + ); + + expect(screen.getByText(/No response has been recorded/)).toBeTruthy(); + expect(screen.queryByText(/The daemon responded without a usable result/)).toBeNull(); +}); diff --git a/dashboard/src/ui/ReadSection.tsx b/dashboard/src/ui/ReadSection.tsx index 573c7f4a29..1b0cee6837 100644 --- a/dashboard/src/ui/ReadSection.tsx +++ b/dashboard/src/ui/ReadSection.tsx @@ -31,6 +31,8 @@ export type ReadState state: BlockedState; /** Whatever the source said about this state, and nothing where it said nothing. */ detail?: string | undefined; + /** A decoded response can report unknown without supplying a payload. */ + responseReceived?: boolean | undefined; /** * The decoded body a *source-level* refusal still carried. * @@ -112,7 +114,10 @@ export function envelopeReadState( return { kind: 'blocked', state: result.state, - detail: result.detail ?? details.transport ?? 'daemon unreachable', + detail: result.detail ?? (result.responseReceived + ? 'no usable result was provided' + : details.transport ?? 'daemon unreachable'), + responseReceived: result.responseReceived, }; } return { kind: 'ready', value: result.envelope }; @@ -150,7 +155,7 @@ export function ReadSection({ return state.kind === 'ready' ? ( <>{children(state.value)} ) : ( - + ); } return ( @@ -315,12 +320,19 @@ export function CenteredState({ title, kind, detail, + responseReceived, }: { title: string; kind: DomainStateKind; detail?: string | undefined; + responseReceived?: boolean | undefined; }) { - const guidance = STATE_GUIDANCE[kind]; + const guidance = kind === 'unknown' && responseReceived + ? { + sentence: 'The daemon responded without a usable result for this read.', + action: 'The reported reason describes why no result is shown.', + } + : STATE_GUIDANCE[kind]; // A dead channel on an instrument still shows its ruled field and its bezel: // the reader can see the surface is present and simply carrying no signal. // Sized by its CONTAINER, not the viewport: this plate renders both on From 8a9bb56a579d075c0e34ae828dcd6f418272a9c3 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 17:20:37 -0700 Subject: [PATCH 22/29] fix(dashboard): preserve completed transcript page coverage --- crates/tracedecay-dashboard-api/src/lcm_api.rs | 12 ++++-------- .../src/lcm_api/aggregates.rs | 7 ------- crates/tracedecay/tests/dashboard_api_test/api.rs | 9 +++++++++ 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/crates/tracedecay-dashboard-api/src/lcm_api.rs b/crates/tracedecay-dashboard-api/src/lcm_api.rs index 1a3a008438..ea848d2a56 100644 --- a/crates/tracedecay-dashboard-api/src/lcm_api.rs +++ b/crates/tracedecay-dashboard-api/src/lcm_api.rs @@ -573,14 +573,10 @@ where match outcome { DashboardLcmReadOutcomeV1::Ready(page) => { let timeline_coverage = aggregates::timeline_view_coverage(&request, &page); - let coverage = if aggregates::is_aggregate_request(&request) { - DashboardCoverageV1::complete( - aggregates::returned_count(&page), - "canonical hydrated records", - ) - } else { - DashboardCoverageV1::unknown() - }; + let coverage = DashboardCoverageV1::complete( + aggregates::returned_count(&page), + "canonical hydrated records", + ); match aggregates::render_canonical_payload( request, page, diff --git a/crates/tracedecay-dashboard-api/src/lcm_api/aggregates.rs b/crates/tracedecay-dashboard-api/src/lcm_api/aggregates.rs index 2f15f7321a..02051603f4 100644 --- a/crates/tracedecay-dashboard-api/src/lcm_api/aggregates.rs +++ b/crates/tracedecay-dashboard-api/src/lcm_api/aggregates.rs @@ -472,13 +472,6 @@ fn timeline_json( }) } -pub(super) fn is_aggregate_request(request: &DashboardLcmReadRequestV1) -> bool { - matches!( - request, - DashboardLcmReadRequestV1::Overview { .. } | DashboardLcmReadRequestV1::Timeline { .. } - ) -} - pub(super) fn timeline_view_coverage( request: &DashboardLcmReadRequestV1, page: &DashboardLcmCanonicalPageV1, diff --git a/crates/tracedecay/tests/dashboard_api_test/api.rs b/crates/tracedecay/tests/dashboard_api_test/api.rs index 88313d13ec..6cd62d8899 100644 --- a/crates/tracedecay/tests/dashboard_api_test/api.rs +++ b/crates/tracedecay/tests/dashboard_api_test/api.rs @@ -969,6 +969,9 @@ fn lcm_large_session_pages_preserve_continuation_and_timeline_counts() { .as_array() .unwrap_or_else(|| panic!("large session must serve a bounded page: {first}")); assert_eq!(first_messages.len(), 64); + assert_eq!(first["coverage"]["completeness"], "partial", "{first}"); + assert_eq!(first["coverage"]["examined"], 64, "{first}"); + assert_eq!(first["payload"]["counts"]["message_count"], 70, "{first}"); let cursor = first["payload"]["next_cursor"] .as_str() .expect("bounded page continuation"); @@ -980,6 +983,12 @@ fn lcm_large_session_pages_preserve_continuation_and_timeline_counts() { .unwrap_or_else(|| panic!("continuation must serve remaining messages: {second}")); assert_eq!(second_messages.len(), 6); assert!(second["payload"]["next_cursor"].is_null()); + assert_eq!(second["coverage"]["completeness"], "complete", "{second}"); + for field in ["eligible", "examined", "matched"] { + assert_eq!(second["coverage"][field], 6, "{second}"); + } + assert_eq!(second["coverage"]["omitted"], 0, "{second}"); + assert_eq!(second["payload"]["counts"]["message_count"], 70, "{second}"); let ids = first_messages .iter() .chain(second_messages) From 9ddae8cb7c951104598ae5443582c586c97769d6 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 17:35:58 -0700 Subject: [PATCH 23/29] fix(sessions): reserve foreground readers during convergence --- .../src/lcm_summary_convergence.rs | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/crates/tracedecay-session-runtime/src/lcm_summary_convergence.rs b/crates/tracedecay-session-runtime/src/lcm_summary_convergence.rs index 7c0e0124dd..5d87545c05 100644 --- a/crates/tracedecay-session-runtime/src/lcm_summary_convergence.rs +++ b/crates/tracedecay-session-runtime/src/lcm_summary_convergence.rs @@ -136,6 +136,8 @@ async fn backfill_queue( // An idle pass must not take the writer. The probe is only a hint: the // page re-reads the authoritative frontier under the writer transaction. let snapshot = database + .read_connection() + .background() .read_snapshot() .await .map_err(|error| LcmError::Db(error.to_string()))?; @@ -171,6 +173,8 @@ async fn requeue_parked_sessions( ) -> Result { let binding = crate::lcm_summarization::summarizer_binding_identity(database)?; let snapshot = database + .read_connection() + .background() .read_snapshot() .await .map_err(|error| LcmError::Db(error.to_string()))?; @@ -208,6 +212,8 @@ async fn rewrite_predecessor_ranges( database: &RegisteredGlobalDbLeaseV1, ) -> Result { let snapshot = database + .read_connection() + .background() .read_snapshot() .await .map_err(|error| LcmError::Db(error.to_string()))?; @@ -238,6 +244,8 @@ async fn load_candidate( now_unix_ms: i64, ) -> Result, LcmError> { let snapshot = database + .read_connection() + .background() .read_snapshot() .await .map_err(|error| LcmError::Db(error.to_string()))?; @@ -250,6 +258,8 @@ async fn load_session_candidate( session_id: &str, ) -> Result, LcmError> { let snapshot = database + .read_connection() + .background() .read_snapshot() .await .map_err(|error| LcmError::Db(error.to_string()))?; @@ -259,6 +269,8 @@ async fn load_session_candidate( async fn load_next_retry(database: &RegisteredGlobalDbLeaseV1) -> Result, LcmError> { let snapshot = database + .read_connection() + .background() .read_snapshot() .await .map_err(|error| LcmError::Db(error.to_string()))?; @@ -621,8 +633,48 @@ fn duration_millis(duration: Duration) -> Result { #[cfg(test)] mod tests { - use super::is_retryable; + use super::{is_retryable, load_candidate}; + use tracedecay_global_db::tests::harness::RegisteredGlobalDbHarness; use tracedecay_lcm::LcmError; + use tracedecay_runtime_core::db::engine::{Error as EngineError, params}; + + #[tokio::test] + async fn convergence_reads_leave_foreground_reader_capacity_available() { + let harness = RegisteredGlobalDbHarness::open("lcm-convergence-reader-admission").await; + let database = &harness.registered; + assert!(load_candidate(database, 0).await.unwrap().is_none()); + + let background = database.read_connection().background(); + let mut held = Vec::new(); + loop { + match background.read_snapshot().await { + Ok(snapshot) => held.push(snapshot), + Err(EngineError::Runtime(reason)) => { + assert_eq!(reason, "exact SQL reader lane is saturated"); + break; + } + Err(error) => panic!("unexpected background admission failure: {error}"), + } + } + assert!(!held.is_empty(), "idle background reads must be admitted"); + + let foreground = database.read_snapshot().await.unwrap(); + let mut rows = foreground.query("SELECT 1", params![]).await.unwrap(); + assert_eq!( + rows.next().await.unwrap().unwrap().get::(0).unwrap(), + 1 + ); + drop(rows); + foreground.commit().await.unwrap(); + assert!( + matches!(load_candidate(database, 0).await, Err(LcmError::Db(reason)) if reason.contains("exact SQL reader lane is saturated")), + "background convergence must not consume the foreground reservation" + ); + for snapshot in held { + snapshot.commit().await.unwrap(); + } + assert!(load_candidate(database, 0).await.unwrap().is_none()); + } #[test] fn convergence_error_classification_preserves_retry_truth() { From 2288d2669e134b0a382a37d3f4e4de5150150b1f Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 18:36:11 -0700 Subject: [PATCH 24/29] fix(chatgpt): refresh embedded app for locked build inputs --- plugin/chatgpt-extension/embedded/app.html | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/plugin/chatgpt-extension/embedded/app.html b/plugin/chatgpt-extension/embedded/app.html index d68cb59e33..2371c9d6e6 100644 --- a/plugin/chatgpt-extension/embedded/app.html +++ b/plugin/chatgpt-extension/embedded/app.html @@ -4,12 +4,12 @@ TraceDecay Code Explorer - +
- +- "app": Tool callable by the app from this server only`),csp:a.never().optional(),permissions:a.never().optional()}),a.object({mimeTypes:a.array(a.string()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')}),a.object({method:a.literal("ui/download-file"),params:a.object({contents:a.array(a.union([eL,eR])).describe("Resource contents to download — embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})}),a.object({method:a.literal("ui/message"),params:a.object({role:a.literal("user").describe('Message role, currently only "user" is supported.'),content:a.array(eM).describe("Message content blocks (text, image, etc.).")})}),a.object({method:a.literal("ui/notifications/sandbox-resource-ready"),params:a.object({html:a.string().describe("HTML content to load into the inner iframe."),sandbox:a.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:tX.optional().describe("CSP configuration from resource metadata."),permissions:tY.optional().describe("Sandbox permissions from resource metadata.")})}),a.object({method:a.literal("ui/notifications/tool-result"),params:eG.describe("Standard MCP tool execution result.")})),it=a.object({toolInfo:a.object({id:b.optional().describe("JSON-RPC id of the tools/call request."),tool:eH.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:tF.optional().describe("Current color theme preference."),styles:t1.optional().describe("Style configuration for theming the app."),displayMode:tJ.optional().describe("How the UI is currently displayed."),availableDisplayModes:a.array(tJ).optional().describe("Display modes the host supports."),containerDimensions:a.union([a.object({height:a.number().describe("Fixed container height in pixels.")}),a.object({maxHeight:a.union([a.number(),a.undefined()]).optional().describe("Maximum container height in pixels.")})]).and(a.union([a.object({width:a.number().describe("Fixed container width in pixels.")}),a.object({maxWidth:a.union([a.number(),a.undefined()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other +container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:a.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:a.string().optional().describe("User's timezone in IANA format."),userAgent:a.string().optional().describe("Host application identifier."),platform:a.union([a.literal("web"),a.literal("desktop"),a.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:a.object({touch:a.boolean().optional().describe("Whether the device supports touch input."),hover:a.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:a.object({top:a.number().describe("Top safe area inset in pixels."),right:a.number().describe("Right safe area inset in pixels."),bottom:a.number().describe("Bottom safe area inset in pixels."),left:a.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),ii=a.object({method:a.literal("ui/notifications/host-context-changed"),params:it.describe("Partial context update containing only changed fields.")}),ir=(a.object({method:a.literal("ui/update-model-context"),params:a.object({content:a.array(eM).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:a.record(a.string(),a.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),a.object({method:a.literal("ui/initialize"),params:a.object({appInfo:D.describe("App identification (name and version)."),appCapabilities:t7.describe("Features and capabilities this app provides."),protocolVersion:a.string().describe("Protocol version this app supports.")})}),a.object({protocolVersion:a.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:D.describe("Host application identification and version."),hostCapabilities:t3.describe("Features and capabilities provided by the host."),hostContext:it.describe("Rich context about the host environment.")}).passthrough()),ia={target:"draft-2020-12"};async function io(e,t){let r=e["~standard"];if(r.jsonSchema)return r.jsonSchema[t](ia);if("zod"===r.vendor){let{z:r}=await Promise.resolve().then(i.bind(i,"H"));return r.toJSONSchema(e,{io:t})}throw Error(`Schema (vendor: ${r.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function is(e,t,i=""){let r=await e["~standard"].validate(t);if(r.issues)throw Error(i+r.issues.map(e=>{let t=e.path?.map(e=>"object"==typeof e?e.key:e).join(".");return t?`${t}: ${e.message}`:e.message}).join("; "));return r.value}class il extends tV{_appInfo;_capabilities;options;_hostCapabilities;_hostInfo;_hostContext;_registeredTools={};_initializedSent=!1;_assertInitialized(e){if(this._initializedSent)return;let t=`[ext-apps] App.${e}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if(this.options?.strict)throw Error(t);console.warn(`${t}. This will throw in a future release.`)}eventSchemas={toolinput:tQ,toolinputpartial:t0,toolresult:ie,toolcancelled:t4,hostcontextchanged:ii};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(e){if(!il.ONE_SHOT_EVENTS.has(e)||this._everHadListener.has(e)||(this._everHadListener.add(e),!this._initializedSent))return;let t=`[ext-apps] "${String(e)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if(this.options?.strict)throw Error(t);console.warn(t)}setEventHandler(e,t){t&&this._assertHandlerTiming(e),super.setEventHandler(e,t)}addEventListener(e,t){this._assertHandlerTiming(e),super.addEventListener(e,t)}onEventDispatch(e,t){"hostcontextchanged"===e&&(this._hostContext={...this._hostContext,...t})}constructor(e,t={},i={autoResize:!0}){super(i),this._appInfo=e,this._capabilities=t,this.options=i,i.allowUnsafeEval||tM.$W({jitless:!0}),this.setRequestHandler(F,e=>(console.log("Received ping:",e.params),{})),this.setEventHandler("hostcontextchanged",void 0)}registerCapabilities(e){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._capabilities=function(e,t){let i={...e};for(let e in t){let r=t[e];if(void 0===r)continue;let n=i[e];tR(n)&&tR(r)?i[e]={...n,...r}:i[e]=r}return i}(this._capabilities,e)}registerTool(e,t,i){if(this._registeredTools[e])throw Error(`Tool ${e} is already registered`);let r=this,n=()=>{r._initializedSent&&r._capabilities.tools?.listChanged&&r.sendToolListChanged()},a=void 0!==t.inputSchema,o={title:t.title,description:t.description,inputSchema:t.inputSchema,outputSchema:t.outputSchema,annotations:t.annotations,_meta:t._meta,enabled:!0,enable(){this.enabled=!0,n()},disable(){this.enabled=!1,n()},update(e){Object.assign(this,e),n()},remove(){r._registeredTools[e]===o&&(delete r._registeredTools[e],n())},handler:async(t,r)=>{let n;if(!o.enabled)throw Error(`Tool ${e} is disabled`);if(a){let a=o.inputSchema,s=a?await is(a,t??{},`Invalid input for tool ${e}: `):t??{};n=await i(s,r)}else n=await i(r);return o.outputSchema&&!n.isError&&(n.structuredContent=await is(o.outputSchema,n.structuredContent,`Invalid output for tool ${e}: `)),n}};return this._registeredTools[e]=o,this._capabilities.tools||this.transport||this.registerCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),n(),o}_toolHandlersInitialized=!1;ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(e,t)=>{let i=this._registeredTools[e.name];if(!i)throw Error(`Tool ${e.name} not found`);return i.handler(e.arguments,t)},this.onlisttools=async(e,t)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([e,t])=>t.enabled).map(async([e,t])=>{let i={name:e,title:t.title,description:t.description,inputSchema:t.inputSchema?await io(t.inputSchema,"input"):{type:"object",properties:{}}};return t.outputSchema&&(i.outputSchema=await io(t.outputSchema,"output")),t.annotations&&(i.annotations=t.annotations),t._meta&&(i._meta=t._meta),i}))}))}async sendToolListChanged(e={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:e})}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(e){this.setEventHandler("toolinput",e)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(e){this.setEventHandler("toolinputpartial",e)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(e){this.setEventHandler("toolresult",e)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(e){this.setEventHandler("toolcancelled",e)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(e){this.setEventHandler("hostcontextchanged",e)}_onteardown;get onteardown(){return this._onteardown}set onteardown(e){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,e),this._onteardown=e,this.replaceRequestHandler(t2,(e,t)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(e.params,t)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(e){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,e),this._oncalltool=e,this.replaceRequestHandler(eY,(e,t)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(e.params,t)})}_onlisttools;get onlisttools(){return this._onlisttools}set onlisttools(e){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,e),this._onlisttools=e,this.replaceRequestHandler(eK,(e,t)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(e.params,t)})}assertCapabilityForMethod(e){if("sampling/createMessage"===e&&!this._hostCapabilities?.sampling)throw Error(`Host does not support sampling (required for ${e})`)}assertRequestHandlerCapability(e){switch(e){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${e})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${e} registered`)}}assertNotificationCapability(e){}assertTaskCapability(e){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(e){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(e,t){if(this._assertInitialized("callServerTool"),"string"==typeof e)throw Error(`callServerTool() expects an object as its first argument, but received a string ("${e}"). Did you mean: callServerTool({ name: "${e}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:e},eG,{onprogress:()=>{},resetTimeoutOnProgress:!0,...t})}async readServerResource(e,t){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:e},eI,t)}async listServerResources(e,t){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:e},e$,t)}async createSamplingMessage(e,t){this._assertInitialized("createSamplingMessage");let i=e.tools?ta:tn;return await this.request({method:"sampling/createMessage",params:e},i,t)}sendMessage(e,t){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:e},tG,t)}sendLog(e){return this.notification({method:"notifications/message",params:e})}updateModelContext(e,t){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:e},z,t)}openLink(e,t){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:e},tK,t)}sendOpenLink=this.openLink;downloadFile(e,t){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:e},tW,t)}requestTeardown(e={}){return this.notification({method:"ui/notifications/request-teardown",params:e})}requestDisplayMode(e,t){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:e},t5,t)}sendSizeChanged(e){return this.notification({method:"ui/notifications/size-changed",params:e})}setupSizeChangedNotifications(){let e=!1,t=0,i=0,r=()=>{e||(e=!0,requestAnimationFrame(()=>{e=!1;let r=document.documentElement,n=r.style.height;r.style.height="max-content";let a=Math.ceil(r.getBoundingClientRect().height);r.style.height=n;let o=Math.ceil(window.innerWidth);(o!==t||a!==i)&&(t=o,i=a,this.sendSizeChanged({width:o,height:a}))}))};r();let n=new ResizeObserver(r);return n.observe(document.documentElement),n.observe(document.body),()=>n.disconnect()}async connect(e=new tq(window.parent,window.parent),t){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1,await super.connect(e);try{let e=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:"2026-01-26"}},ir,t);if(void 0===e)throw Error(`Server sent invalid initialize result: ${e}`);this._hostCapabilities=e.hostCapabilities,this._hostInfo=e.hostInfo,this._hostContext=e.hostContext,await this.notification({method:"ui/notifications/initialized"}),this._initializedSent=!0,this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(e){throw this.close(),e}}}let iu=new Set(["projects","search","symbol","failure"]);function id(e){switch(e.kind){case"projects":return"/";case"project":return`/projects/${encodeURIComponent(e.project_id)}`;case"search":return`/projects/${encodeURIComponent(e.project_id)}/search?q=${encodeURIComponent(e.query)}`;case"symbol":return`/projects/${encodeURIComponent(e.project_id)}/symbols/${encodeURIComponent(e.node_id)}`}}let ic=document.getElementById("root");if(null===ic)throw Error("app root element missing");let im=new il({name:"tracedecay-code-explorer",version:"0.0.0"},{availableDisplayModes:["inline","fullscreen"]}),ip={phase:"connecting"},ig=[],iv=null;function ih(e){ip=e,iM()}function i$(){return"view"===ip.phase?ip.view:"loading"===ip.phase?ip.previous:null}function iy(e){"projects"===e.page&&"ready"===e.projects.state&&(ig=e.projects.data),ih({phase:"view",view:e}),ix(e)}function ib(e){var t;return"object"==typeof(t=e.structuredContent)&&null!==t&&"page"in t&&"string"==typeof t.page&&iu.has(t.page)?e.structuredContent:null}async function i_(e,t,i){ih({phase:"loading",label:i,previous:i$()});try{let i=await im.callServerTool({name:e,arguments:t}),r=ib(i);if(null===r)return void ih({phase:"host_error",message:`${e} returned no view payload`});iy(r)}catch(e){ih({phase:"host_error",message:e instanceof Error?e.message:String(e)})}}async function ix(e){let t=function(e){switch(e.page){case"projects":return null;case"search":var t;return{content:[{type:"text",text:`TraceDecay search in ${e.project.label} for "${e.query}": ${function(e,t){switch(e.state){case"ready":return t(e.data);case"empty":return e.message;case"failed":return`${e.failure.kind}: ${e.failure.message}`}}(e.results,e=>`${e.hits.length} hit(s), recall ${e.recall}${e.undisplayable.length>0?`, ${e.undisplayable.length} undisplayable`:""}`)}${null===(t=e.provenance)?"":` [reported project status: branch ${t.branch??"?"} commit ${t.commit??"?"} generation ${t.generation??"?"} freshness ${t.freshness.state}]`}`}],structuredContent:{page:"search",project_id:e.project.project_id,query:e.query,deep_link:id({kind:"search",project_id:e.project.project_id,query:e.query}),provenance:e.provenance}};case"symbol":if(null===e.evidence)return null;return{content:[{type:"text",text:e.evidence.markdown}],structuredContent:e.evidence.structured};case"failure":return{content:[{type:"text",text:`TraceDecay could not answer (${e.failure.kind}/${e.failure.code}): ${e.failure.message}`}],structuredContent:{page:"failure",failure:e.failure}}}}(e);if(null!==t)try{await im.updateModelContext(t)}catch{}}function iI(){i_("tracedecay_workspace",{},"Loading registered projects…")}function ik(e){iy({page:"search",project:e,query:"",results:{state:"empty",message:"Type a symbol name or phrase to search this project."},provenance:null})}function iw(e,t){i_("tracedecay_search_code",{project_id:e,query:t},`Searching for "${t}"…`)}function iS(e,t){i_("tracedecay_inspect_symbol",{project_id:e,node_id:t},"Loading symbol…")}function iz(e){let t,i="string"==typeof(t=Reflect.get(e,"openai/deepLink"))?t:"object"==typeof t&&null!==t&&"url"in t&&"string"==typeof t.url?t.url:null;if(null===i||i===iv)return;iv=i;let r=function(e){let t;try{t=new URL(e,"https://tracedecay.invalid")}catch{return null}let i=t.pathname.split("/").filter(e=>e.length>0).map(decodeURIComponent);if(0===i.length)return{kind:"projects"};if("projects"!==i[0]||void 0===i[1])return null;let r=i[1];return 2===i.length?{kind:"project",project_id:r}:"search"===i[2]&&3===i.length?{kind:"search",project_id:r,query:t.searchParams.get("q")??""}:"symbols"===i[2]&&void 0!==i[3]&&4===i.length?{kind:"symbol",project_id:r,node_id:i[3]}:null}(i);null!==r&&function(e){switch(e.kind){case"projects":iI();return;case"project":{let t=ig.find(t=>t.project_id===e.project_id);void 0!==t?ik(t):iw(e.project_id,"");return}case"search":iw(e.project_id,e.query);return;case"symbol":iS(e.project_id,e.node_id);return}}(r)}function ij(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],r=document.createElement(e);for(let[e,i]of Object.entries(t))r.setAttribute(e,i);return r.append(...i),r}function iP(e,t){let i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=ij("button",{type:"button",...i},[e]);return r.addEventListener("click",t),r}function iN(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"neutral",i=arguments.length>2?arguments[2]:void 0,r={class:"neutral"===t?"badge":`badge ${t}`};return void 0!==i&&(r["data-testid"]=i),ij("span",r,[e])}function iU(e){return"stale"===e.kind||"unavailable"===e.kind?"warn":"bad"}function iO(e){switch(e){case"current":case"fresh":return"ok";case"stale":case"possibly_stale":case"warming":case"restoring":return"warn";case"parked":case"unavailable":return"bad"}}function iD(e,t,i){return ij("p",{class:"neutral"===t?"notice":`notice ${t}`,"data-testid":i},[e])}function iT(e,t){let i=iD(`${e.message} (${e.code})`,iU(e),t);return i.prepend(iN(e.kind,iU(e),`${t}-kind`)," "),i}function iA(e,t,i){switch(e.state){case"ready":return i(e.data);case"empty":return iD(e.message,"neutral",`${t}-empty`);case"failed":return iT(e.failure,`${t}-failed`)}}function iZ(e,t,i){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:[];return ij("section",{class:"panel","data-testid":t},[ij("h2",{},[e,...r]),...i])}function iE(e,t){return iP(t.qualified_name||t.name,()=>iS(e,t.node_id),{class:"mono","data-testid":"symbol-link","data-node-id":t.node_id})}function iC(e){let t=null===e.start_line?"":`:${e.start_line}`;return ij("span",{class:"loc"},[`${e.file}${t}`])}function iL(e,t){let i=ij("input",{type:"search",name:"q",placeholder:`Search ${e.label}…`,value:t,"data-testid":"search-input","aria-label":"Search query"}),r=()=>{let t=i.value.trim();t.length>0&&iw(e.project_id,t)},n=ij("form",{class:"search","data-testid":"search-form"},[i,iP("Search",r,{class:"primary","data-testid":"search-submit"})]);return i.addEventListener("keydown",e=>{"Enter"===e.key&&(e.preventDefault(),r())}),n.addEventListener("submit",e=>e.preventDefault()),n}function iR(e,t){if(null===e)return iZ("Project status","provenance",[iD("Project status is reported once the project answers a request.","neutral","provenance-pending")]);let i=[["project",ij("code",{},[e.project_id])],["worktree",ij("code",{},[e.worktree_id??e.project_root])],["repository",ij("code",{},[e.repository_id??"—"])],["ref",e.reference??e.branch??"—"],["commit",ij("code",{"data-testid":"provenance-commit"},[e.commit??"—"])],["generation",ij("code",{},[e.generation??"—"])],["coverage",ij("span",{},[iN(e.coverage.recall,"full"===e.coverage.recall?"ok":"warn","coverage-badge"),null===e.coverage.detail?"":` ${e.coverage.detail}`])],["daemon",null===e.authority?"unavailable":`${e.authority.daemon_version} \xb7 pid ${e.authority.daemon_pid}`],["deep link",ij("code",{"data-testid":"deep-link"},[id(t)])]],r=ij("dl",{class:"prov"},i.flatMap(e=>{let[t,i]=e;return[ij("dt",{},[t]),ij("dd",{},[i])]})),n=iN(e.freshness.state,iO(e.freshness.state),"freshness-badge"),a=[r];return null!==e.freshness.detail&&a.unshift(iD(e.freshness.detail,"ok"===iO(e.freshness.state)?"neutral":"warn","freshness-detail")),iZ("Project status (separate read)","provenance",a,[n])}function iM(){ic.replaceChildren(function(e){let t=ij("nav",{class:"crumbs","aria-label":"Breadcrumb"});t.append(iP("Projects",iI,{"data-testid":"crumb-projects"}));let i=null!==e&&"projects"!==e.page?e.project:null;null!==i&&t.append("/",iP(i.label,()=>ik(i),{"data-testid":"crumb-project"})),e?.page==="search"&&""!==e.query&&t.append("/",ij("span",{},[`"${e.query}"`])),e?.page==="symbol"&&"ready"===e.symbol.state&&t.append("/",ij("span",{class:"mono"},[e.symbol.data.name]));let r=ij("header",{class:"bar"},[ij("h1",{},["TraceDecay"]),t,ij("span",{class:"spacer"})]),n=im.getHostContext()?.displayMode;if(void 0!==n){let e="fullscreen"===n?"inline":"fullscreen";r.append(iP("fullscreen"===e?"Expand":"Collapse",()=>void im.requestDisplayMode({mode:e}),{"data-testid":"toggle-display-mode"}))}return r}(i$()),...function(){switch(ip.phase){case"connecting":return[ij("p",{class:"loading","data-testid":"connecting"},["Connecting to ChatGPT…"])];case"loading":return[ij("p",{class:"loading","data-testid":"loading"},[ip.label])];case"host_error":return[iT({kind:"protocol",code:"host_call_failed",message:ip.message},"host-error"),ij("div",{class:"actions"},[iP("Back to projects",iI,{class:"primary","data-testid":"back-to-projects"})])];case"view":var e,t,i,r;switch(ip.view.page){case"projects":return["connected"===(e=ip.view).daemon.state?ij("p",{class:"notice","data-testid":"daemon-state"},[iN("connected","ok","daemon-badge"),` tracedecay ${e.daemon.version} (pid ${e.daemon.pid}) \xb7 profile `,ij("code",{},[e.daemon.profile_root])]):iT(e.daemon.failure,"daemon-state"),iZ("Recent registered projects","projects-panel",[iA(e.projects,"projects",e=>ij("ul",{class:"list"},e.map(e=>ij("li",{"data-testid":"project-row"},[iP(e.label,()=>ik(e),{class:"primary","data-testid":"project-open","data-project-id":e.project_id}),ij("span",{class:"loc"},[e.project_root]),null===e.head_branch?"":iN(e.head_branch)])))),ij("p",{class:"notice"},["Showing up to 10 recently used projects. Only projects registered in this TraceDecay profile (via `tracedecay init`) can be explored. Nothing here writes to your code."])])];case"search":let n;return n=iA((t=ip.view).results,"results",e=>{let i=[ij("ul",{class:"list"},e.hits.map(e=>ij("li",{"data-testid":"search-hit"},[iN(e.kind),iE(t.project.project_id,e),iC(e)])))];if("partial"===e.recall&&i.push(iD("Partial recall: the index is still converging, so some matches may be missing.","warn","results-partial")),e.truncated&&i.push(iD("Results were truncated to the first page.","neutral","results-truncated")),e.undisplayable.length>0){let t=[...new Set(e.undisplayable)].join(", ");i.push(iD(`${e.undisplayable.length} candidate(s) could not be displayed (${t}).`,"warn","results-undisplayable"))}return"ready"===t.results.state&&void 0!==t.results.generation&&t.provenance?.generation!==void 0&&t.results.generation!==t.provenance.generation&&i.push(iD(`Results were served from generation ${t.results.generation}, which differs from the reported project generation ${t.provenance.generation}.`,"warn","results-generation")),ij("div",{},i)}),[iL(t.project,t.query),iZ(""===t.query?"Search":`Results for "${t.query}"`,"results-panel",[n]),iR(t.provenance,{kind:"search",project_id:t.project.project_id,query:t.query})];case"symbol":let a,o,s,l,u,d,c;return a=(i=ip.view).project.project_id,o=iA(i.symbol,"symbol",e=>{let t=[ij("p",{},[iN(e.kind)," ",ij("code",{"data-testid":"symbol-qualified-name"},[e.qualified_name])," ",iC(e)])];return null!==e.signature&&t.push(ij("pre",{class:"mono"},[e.signature])),null!==e.complexity&&t.push(ij("p",{class:"notice"},[`Cyclomatic complexity ${e.complexity}`])),e.unavailable_fields.length>0&&t.push(iD(`Not available for this symbol: ${e.unavailable_fields.join(", ")}`,"neutral","symbol-unavailable-fields")),ij("div",{},t)}),s=(e,t)=>iA(e,t,r=>{let n=[ij("ul",{class:"list"},r.map(e=>ij("li",{"data-testid":`${t}-row`},[iN(e.edge_kind),iE(a,e.symbol),iC(e.symbol),e.dispatch_via_trait?iN("via trait"):""])))];return"ready"===e.state&&!0===e.truncated&&n.push(iD("Only the first page of relations is shown.","neutral",`${t}-truncated`)),"ready"===e.state&&void 0!==e.generation&&i.provenance?.generation!==void 0&&e.generation!==i.provenance.generation&&n.push(iD(`Served from generation ${e.generation}, which differs from the reported project generation ${i.provenance.generation}.`,"warn",`${t}-generation`)),ij("div",{},n)}),l=iA(i.impact,"impact",e=>{let t=[ij("ul",{class:"list"},e.nodes.map(e=>ij("li",{"data-testid":"impact-row"},[iN(`depth ${e.depth}`),iP(e.name,()=>iS(a,e.node_id),{class:"mono"}),ij("span",{class:"loc"},[`${e.file}:${e.line}`])])))];return t.push(e.complete?iD(`Complete within depth ${e.max_depth}.`,"neutral","impact-complete"):iD(`Partial: traversal stopped at depth ${e.max_depth}.`,"warn","impact-partial")),ij("div",{},t)}),u=iA(i.graph,"graph",e=>(function(e,t){let i=t.nodes.filter(e=>"caller"===e.role),r=t.nodes.filter(e=>"callee"===e.role),n=t.nodes.find(e=>"focus"===e.role),a=28*Math.max(i.length,r.length,1)+20,o="http://www.w3.org/2000/svg",s=document.createElementNS(o,"svg");s.setAttribute("class","graph"),s.setAttribute("viewBox",`0 0 640 ${a}`),s.setAttribute("role","img"),s.setAttribute("aria-label","Bounded caller/callee graph"),s.dataset.testid="graph";let l={caller:10,focus:220,callee:430},u=new Map,d=(t,i,r)=>{let n=l[r],d="focus"===r?a/2-10:10+28*i;u.set(t.node_id,{x:n,y:d});let c=document.createElementNS(o,"g");c.setAttribute("class",`node ${t.role}`),c.dataset.testid="graph-node",c.dataset.nodeId=t.node_id;let m=document.createElementNS(o,"rect");m.setAttribute("x",String(n)),m.setAttribute("y",String(d)),m.setAttribute("width","200"),m.setAttribute("height","20"),m.setAttribute("rx","4");let f=document.createElementNS(o,"text");f.setAttribute("x",String(n+6)),f.setAttribute("y",String(d+14)),f.textContent=t.name.length>28?`${t.name.slice(0,27)}…`:t.name;let p=document.createElementNS(o,"title");p.textContent=`${t.qualified_name} (${t.file})`,c.append(p,m,f),"focus"!==t.role&&c.addEventListener("click",()=>iS(e,t.node_id)),s.append(c)};for(let e of(i.forEach((e,t)=>d(e,t,"caller")),r.forEach((e,t)=>d(e,t,"callee")),void 0!==n&&d(n,0,"focus"),t.edges)){let t=u.get(e.from),i=u.get(e.to);if(void 0===t||void 0===i)continue;let r=document.createElementNS(o,"path");r.setAttribute("class","edge"),r.setAttribute("d",`M ${t.x+200} ${t.y+10} C ${t.x+240} ${t.y+10}, ${i.x-40} ${i.y+10}, ${i.x} ${i.y+10}`),s.prepend(r)}let c=ij("div",{},[s]);return t.truncated&&c.append(iD(`Graph bounded to depth ${t.max_depth}; more relations exist than shown.`,"neutral","graph-truncated")),c})(a,e)),d=ij("div",{class:"actions"}),null!==(c=i.evidence)&&d.append(iP("Send evidence to chat",()=>void im.sendMessage({role:"user",content:[{type:"text",text:c.markdown}]}),{class:"primary","data-testid":"send-evidence"}),iP("Re-share with model",()=>void ix(i),{"data-testid":"share-context"})),[iL(i.project,""),iZ("Symbol","symbol-panel",[o,iD("Symbol and impact reads do not report a generation; their snapshot identity is unverified.","neutral","symbol-generation"),d]),iZ("Callers","callers-panel",[s(i.callers,"callers")]),iZ("Callees","callees-panel",[s(i.callees,"callees")]),iZ("Impact","impact-panel",[l]),iZ("Graph","graph-panel",[u]),iR(i.provenance,"ready"===i.symbol.state?{kind:"symbol",project_id:a,node_id:i.symbol.data.node_id}:{kind:"project",project_id:a})];case"failure":let m;return m=[iT((r=ip.view).failure,"page-failure")],"denied"===r.failure.kind&&m.push(iD("Only projects registered in this TraceDecay profile can be explored. Run `tracedecay init` in the repository and reopen the workspace.","neutral","denied-help")),"disconnected"===r.failure.kind&&m.push(iD("Start the daemon with `tracedecay daemon run` (or any tracedecay command) and retry.","neutral","disconnected-help")),m.push(ij("div",{class:"actions"},[iP("Back to projects",iI,{class:"primary","data-testid":"back-to-projects"})])),null!==r.project&&m.unshift(iL(r.project,"")),[iZ("Request failed","failure-panel",m)]}}}()),ic.dataset.phase=ip.phase,ic.dataset.page=i$()?.page??""}im.ontoolinput=e=>{ih({phase:"loading",label:`Loading ${e.arguments?.query!==void 0?`search for "${String(e.arguments.query)}"`:"from TraceDecay"}…`,previous:i$()})},im.ontoolresult=e=>{let t=ib(e);null===t?ih({phase:"host_error",message:"The tool result carried no TraceDecay view payload."}):iy(t)},im.onhostcontextchanged=e=>{iz(e),iM()},iM(),im.connect().then(()=>{let e=im.getHostContext();void 0!==e&&iz(e),"connecting"===ip.phase&&ih({phase:"loading",label:"Waiting for TraceDecay…",previous:null})}).catch(e=>ih({phase:"host_error",message:e instanceof Error?e.message:String(e)}))})()})(); From 38c7d29563ea5915a937d6e146e713f5cfe7921c Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 22:22:50 -0700 Subject: [PATCH 25/29] fix(test): use canonical frontiers and current lifecycle contracts --- .../tests/core_cli_suite/tool_daemon_test.rs | 5 +- .../benches/sealed_storage.rs | 43 ++++++++---- .../benches/session_temporal/harness.rs | 1 - .../session_temporal/root_relation_fixture.rs | 32 ++++++--- .../hook_lifecycle_lease_test.rs | 16 ++++- .../mcp_handler_test/lcm_describe_behavior.rs | 12 ++-- crates/tracedecay/tests/mcp_suite/support.rs | 8 +-- .../temporal_benchmark_runner.rs | 66 +++++++------------ 8 files changed, 104 insertions(+), 79 deletions(-) diff --git a/crates/tracedecay-cli/tests/core_cli_suite/tool_daemon_test.rs b/crates/tracedecay-cli/tests/core_cli_suite/tool_daemon_test.rs index b5aec7e824..16d01971c5 100644 --- a/crates/tracedecay-cli/tests/core_cli_suite/tool_daemon_test.rs +++ b/crates/tracedecay-cli/tests/core_cli_suite/tool_daemon_test.rs @@ -1305,8 +1305,9 @@ fn capture_spool_refusal_names_its_typed_cause_on_stderr() { enroll_native_capture_project(&home_path, &project_path, "proj_capture_typed_cause"); // A directory where the spool's records file belongs is a spool the hook // must refuse; the refusal must say which spool fault it hit. - std::fs::create_dir_all(native_capture_spool_root(&data_root, host).join("records.v1.bin")) - .unwrap(); + let records_path = native_capture_spool_root(&data_root, host).join("records.v1.bin"); + std::fs::remove_file(&records_path).unwrap(); + std::fs::create_dir(&records_path).unwrap(); let output = run_native_capture_hook( &home_path, diff --git a/crates/tracedecay-code-index/benches/sealed_storage.rs b/crates/tracedecay-code-index/benches/sealed_storage.rs index 73a58ba58a..9bee93bff9 100644 --- a/crates/tracedecay-code-index/benches/sealed_storage.rs +++ b/crates/tracedecay-code-index/benches/sealed_storage.rs @@ -134,6 +134,7 @@ impl CodeIndexExecutionControlV1 for ActiveControl { struct Sealed { manifest: Vec, file_segments: BTreeMap>, + segment_kinds: BTreeMap, evidence: (String, Vec), } @@ -288,8 +289,11 @@ fn main() -> Result<(), Box> { let successor_restore_ms = digest_decoded(&successor_sealed, &store_segments, &mut decoded)?; let mut sections = BTreeMap::new(); - for bytes in store_segments.values() { - add_sections(bytes, &mut sections)?; + let mut segment_kinds = clean_sealed.segment_kinds.clone(); + segment_kinds.extend(successor_sealed.segment_kinds.clone()); + for (digest, bytes) in &store_segments { + let kind = segment_kinds.get(digest).ok_or("segment kind is missing")?; + add_sections(bytes, kind, &mut sections)?; } for sealed in [&clean_sealed, &successor_sealed] { add_evidence_sections(&sealed.evidence.1, &mut sections)?; @@ -484,13 +488,11 @@ fn request( content_digest: digest, disposition: SnapshotFileDispositionV1::Present, }); - if changed.contains(&source.logical_path) { - captured_files.push(CodeIndexCapturedFileV1 { - file_occurrence_id: occurrence, - sanitized_bytes: Arc::clone(&source.bytes), - sensitivity_level: SensitivityLevelV1::Public, - }); - } + captured_files.push(CodeIndexCapturedFileV1 { + file_occurrence_id: occurrence, + sanitized_bytes: Arc::clone(&source.bytes), + sensitivity_level: SensitivityLevelV1::Public, + }); } Ok(CodeIndexBuildRequestV1 { snapshot: SanitizedCodeSnapshotV1 { @@ -540,19 +542,28 @@ fn seal( parent: Option<&[u8]>, ) -> Result { let mut file_segments = BTreeMap::new(); + let mut segment_kinds = BTreeMap::new(); let mut pack = Vec::new(); let mut evidence = None; let manifest = publication.encode(parent, |publication| { match publication { - SealedGenerationSegmentPublicationV1::File { digest, bytes } - | SealedGenerationSegmentPublicationV1::FileEvidence { digest, bytes } - | SealedGenerationSegmentPublicationV1::ResolutionIndex { digest, bytes } => { + SealedGenerationSegmentPublicationV1::File { digest, bytes } => { + file_segments.insert(digest.as_str().to_owned(), bytes.to_vec()); + segment_kinds.insert(digest.as_str().to_owned(), "file"); + } + SealedGenerationSegmentPublicationV1::FileEvidence { digest, bytes } => { file_segments.insert(digest.as_str().to_owned(), bytes.to_vec()); + segment_kinds.insert(digest.as_str().to_owned(), "file_evidence"); + } + SealedGenerationSegmentPublicationV1::ResolutionIndex { digest, bytes } => { + file_segments.insert(digest.as_str().to_owned(), bytes.to_vec()); + segment_kinds.insert(digest.as_str().to_owned(), "resolution_index"); } SealedGenerationSegmentPublicationV1::CodeGraphPage { page_digest, bytes, .. } => { file_segments.insert(page_digest.as_str().to_owned(), bytes.to_vec()); + segment_kinds.insert(page_digest.as_str().to_owned(), "code_graph"); } SealedGenerationSegmentPublicationV1::GenerationEvidencePage { bytes, .. } => { pack.extend_from_slice(bytes); @@ -572,6 +583,7 @@ fn seal( Ok(Sealed { manifest, file_segments, + segment_kinds, evidence: evidence.ok_or_else(|| { CodeIndexProductionErrorV1::Contract("sealed generation has no evidence".to_owned()) })?, @@ -654,10 +666,11 @@ fn digest_decoded( } } -/// Split one stored file segment by payload section. The stored bytes are +/// Split one stored segment by its production publication kind and payload section. The stored bytes are /// either the canonical JSON itself or its raw DEFLATE stream. fn add_sections( bytes: &[u8], + kind: &str, sections: &mut BTreeMap, ) -> Result<(), Box> { let json = if bytes.first() == Some(&b'{') { @@ -669,6 +682,10 @@ fn add_sections( }; *sections.entry("total".to_owned()).or_default() += json.len(); let value: serde_json::Value = serde_json::from_slice(&json)?; + if kind != "file" { + *sections.entry(kind.to_owned()).or_default() += json.len(); + return Ok(()); + } let file = value.get("file").ok_or("segment has no file payload")?; for (key, section) in file.as_object().ok_or("file payload is not an object")? { if key == "artifacts" { diff --git a/crates/tracedecay/benches/session_temporal/harness.rs b/crates/tracedecay/benches/session_temporal/harness.rs index eb9b4f1efa..85871ea278 100644 --- a/crates/tracedecay/benches/session_temporal/harness.rs +++ b/crates/tracedecay/benches/session_temporal/harness.rs @@ -787,7 +787,6 @@ async fn prepare_repetition(repetition: usize) -> BenchResult BenchResult<(RequestContext, SessionRequestBinding)>, sessions: Vec, - observation_count: u64, ) -> BenchResult { if sessions.len() != ROOT_RELATION_PARTICIPANT_COUNT { return Err(format!( @@ -55,6 +54,22 @@ pub(super) async fn refresh_sessions( sessions.len() )); } + let mut frontiers = BTreeMap::new(); + let mut cursor = SessionTemporalRefreshDiscoveryCursor::default(); + loop { + let (requests, next, has_more) = SessionTemporalAccess::new(db) + .pending_session_temporal_refresh_page_result(sessions.len(), 1, &cursor) + .await + .map_err(|error| format!("discover root refresh frontiers: {error}"))? + .into_parts(); + for request in requests { + frontiers.insert(request.session_id().clone(), request.target_frontier()); + } + cursor = next; + if !has_more { + break; + } + } let refresh = SessionRefreshService::new( AllowAuthorizer, SessionTemporalStore::new(db), @@ -71,8 +86,9 @@ pub(super) async fn refresh_sessions( Some("codex".to_owned()), TemporalModeV1::Current, RetrievalGrainV1::LogicalMessage, - SessionRefreshFrontierV1::new(observation_count, 0) - .map_err(|error| format!("root refresh frontier: {error}"))?, + frontiers.remove(session_id).ok_or_else(|| { + format!("root session {session_id} has no pending canonical refresh") + })?, ) .map_err(|error| format!("root refresh target: {error}"))?; let (context, binding) = request(session_id)?; diff --git a/crates/tracedecay/tests/hooks_lsp_suite/hook_lifecycle_lease_test.rs b/crates/tracedecay/tests/hooks_lsp_suite/hook_lifecycle_lease_test.rs index 608ee40df3..bd59095cb2 100644 --- a/crates/tracedecay/tests/hooks_lsp_suite/hook_lifecycle_lease_test.rs +++ b/crates/tracedecay/tests/hooks_lsp_suite/hook_lifecycle_lease_test.rs @@ -3,6 +3,10 @@ use std::path::Path; use std::process::{Command, Output, Stdio}; use std::time::{SystemTime, UNIX_EPOCH}; +use tracedecay_runtime_core::lifecycle_lease::{ + ExclusiveLeaseAttempt, try_acquire_exclusive_for_profile, +}; + use super::common::{ apply_tracedecay_home_env, git_program, spawn_tracedecay_daemon, tracedecay_command_with_home, }; @@ -499,7 +503,17 @@ fn native_hook_captures_only_bound_transport_spool_records() { assert_eq!(replay.stdout, expected_stdout, "{hook} replay: {replay:?}"); assert!(replay.stderr.is_empty(), "{hook} replay: {replay:?}"); } - assert!(!home.join(".tracedecay/lifecycle.lock").exists()); + assert!(home.join(".tracedecay/lifecycle.lock").is_file(), "{hook}"); + let maintenance = try_acquire_exclusive_for_profile( + &home.join(".tracedecay"), + "verify completed hook released its lease", + ) + .unwrap(); + assert!( + matches!(maintenance, ExclusiveLeaseAttempt::Acquired(_)), + "{hook} retained its lifecycle lease after exit" + ); + drop(maintenance); assert!(!home.join(".tracedecay/global.db").exists()); assert!(!data_root.join("tracedecay.db").exists()); assert!(!data_root.join("sessions.db").exists()); diff --git a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/lcm_describe_behavior.rs b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/lcm_describe_behavior.rs index ffbabeebf1..c1c616316d 100644 --- a/crates/tracedecay/tests/mcp_suite/mcp_handler_test/lcm_describe_behavior.rs +++ b/crates/tracedecay/tests/mcp_suite/mcp_handler_test/lcm_describe_behavior.rs @@ -570,24 +570,24 @@ fn session_temporal() -> Value { fn source_coverage(mode: &str) -> Value { json!({ - "committed_frontier": 3, + "committed_frontier": 2, "covered_intervals": [], "missing_intervals": [], - "observed_frontier": 3, + "observed_frontier": 2, "reason": {"kind": "caught_up"}, "request": {"mode": {"kind": mode}}, "source_id": "orchard-describe:cursor", "state": "fresh", - "target_watermark": 3 + "target_watermark": 2 }) } fn watermarks() -> Value { json!({ "generation": 3, - "index": 3, - "projection": 3, - "source": 3, + "index": 2, + "projection": 2, + "source": 2, "summary": 1 }) } diff --git a/crates/tracedecay/tests/mcp_suite/support.rs b/crates/tracedecay/tests/mcp_suite/support.rs index 84c694abab..41e68f172d 100644 --- a/crates/tracedecay/tests/mcp_suite/support.rs +++ b/crates/tracedecay/tests/mcp_suite/support.rs @@ -1693,8 +1693,8 @@ pub(crate) async fn persist_temporal_lcm_observation_with_access( }; let source = ObservationSourceIdentityV1::for_provider(provider.clone(), session_id.clone()).unwrap(); - let source_frontier = u64::try_from(ordinal).unwrap().saturating_add(1); - let range = ObservationSourceRangeV1::new(source_frontier - 1, source_frontier).unwrap(); + let source_end = u64::try_from(ordinal).unwrap().saturating_add(1); + let range = ObservationSourceRangeV1::new(source_end - 1, source_end).unwrap(); let stable_record_id = ObservationId::new(format!("record.mcp.{session_id}.{message_id}")).unwrap(); let relations = CanonicalObservationRelationsV1::new(session_id.clone()) @@ -1795,7 +1795,7 @@ pub(crate) async fn persist_temporal_lcm_observation_with_access( durability: base_anchor.durability().clone(), }) .unwrap(); - observation_store + let committed = observation_store .persist_observation( AnchoredObservationWrite::new(write, anchor.clone(), projection_generation).unwrap(), ) @@ -1847,7 +1847,7 @@ pub(crate) async fn persist_temporal_lcm_observation_with_access( .unwrap(); TemporalLcmProjectionInput { occurrence, - source_frontier, + source_frontier: committed.receipt().sequence(), } } diff --git a/crates/tracedecay/tests/session_suite/temporal_benchmark_runner.rs b/crates/tracedecay/tests/session_suite/temporal_benchmark_runner.rs index aac629998f..ee864acc7d 100644 --- a/crates/tracedecay/tests/session_suite/temporal_benchmark_runner.rs +++ b/crates/tracedecay/tests/session_suite/temporal_benchmark_runner.rs @@ -7,13 +7,11 @@ use tracedecay_runtime_core::test_executable::write_executable_script; use tempfile::TempDir; const RUNNER_PATH: &str = "scripts/run-session-temporal-benchmark.sh"; -const FAKE_CARGO_STATUS: i32 = 47; +const FAKE_BAZEL_STATUS: i32 = 47; struct RunnerInvocation { output: Output, - cargo_receipt: Option, - parent_data: PathBuf, - parent_home: PathBuf, + bazel_receipt: Option, } fn write_executable(path: &Path, body: &str) { @@ -24,15 +22,15 @@ fn invoke_runner(uname: &str, mode: &str) -> RunnerInvocation { let temp = TempDir::new().expect("runner tempdir"); let fake_bin = temp.path().join("bin"); fs::create_dir_all(&fake_bin).expect("create fake bin"); - let cargo_receipt_path = temp.path().join("cargo-receipt.txt"); + let bazel_receipt_path = temp.path().join("bazel-receipt.txt"); write_executable( &fake_bin.join("uname"), "#!/bin/sh\nprintf '%s\\n' \"$FAKE_UNAME\"\n", ); write_executable( - &fake_bin.join("cargo"), - "#!/bin/sh\n{\n printf 'argv='\n printf '<%s>' \"$@\"\n printf '\\nHOME=<%s>\\n' \"$HOME\"\n printf 'TRACEDECAY_DATA_DIR=<%s>\\n' \"$TRACEDECAY_DATA_DIR\"\n} >\"$FAKE_CARGO_RECEIPT\"\nexit 47\n", + &fake_bin.join("bazel"), + "#!/bin/sh\n{\n printf 'argv='\n printf '<%s>' \"$@\"\n printf '\\nHOME=<%s>\\n' \"$HOME\"\n printf 'TRACEDECAY_DATA_DIR=<%s>\\n' \"$TRACEDECAY_DATA_DIR\"\n} >\"$FAKE_BAZEL_RECEIPT\"\nexit 47\n", ); let parent_home = temp.path().join("parent-home"); @@ -49,7 +47,7 @@ fn invoke_runner(uname: &str, mode: &str) -> RunnerInvocation { .arg(mode) .current_dir(repository_root()) .env("CARGO_HOME", temp.path().join("cargo-home")) - .env("FAKE_CARGO_RECEIPT", &cargo_receipt_path) + .env("FAKE_BAZEL_RECEIPT", &bazel_receipt_path) .env("FAKE_UNAME", uname) .env("HOME", &parent_home) .env("PATH", fake_path) @@ -59,64 +57,44 @@ fn invoke_runner(uname: &str, mode: &str) -> RunnerInvocation { .output() .expect("run session-temporal runner"); - let cargo_receipt = match fs::read_to_string(&cargo_receipt_path) { + let bazel_receipt = match fs::read_to_string(&bazel_receipt_path) { Ok(receipt) => Some(receipt), Err(error) if error.kind() == std::io::ErrorKind::NotFound => None, - Err(error) => panic!("read cargo receipt: {error}"), + Err(error) => panic!("read Bazel receipt: {error}"), }; RunnerInvocation { output, - cargo_receipt, - parent_data, - parent_home, + bazel_receipt, } } fn repository_root() -> PathBuf { - crate::common::repository_root().to_path_buf() + crate::common::repository_root() + .canonicalize() + .expect("canonical fixture repository root") } #[test] -fn diagnostic_runner_reaches_cargo_on_linux_and_macos() { +fn diagnostic_runner_reaches_bazel_on_linux_and_macos() { for (uname, platform) in [("Linux", "linux"), ("Darwin", "macos")] { let invocation = invoke_runner(uname, "--run"); assert_eq!( invocation.output.status.code(), - Some(FAKE_CARGO_STATUS), + Some(FAKE_BAZEL_STATUS), "{platform} runner output: {}", String::from_utf8_lossy(&invocation.output.stderr) ); let receipt = invocation - .cargo_receipt - .expect("diagnostic runner must execute cargo"); - assert!( - // `eebf0957a` retargeted the shipped runner after the crate move: - // the repository root is a virtual workspace, so `cargo bench` must - // name `-p tracedecay` to resolve the harness at all. - receipt.starts_with( - "argv=<-p><--bench><--all-features><--><--run>\n" + .bazel_receipt + .expect("diagnostic runner must execute Bazel"); + assert_eq!( + receipt.lines().next().expect("Bazel argument receipt"), + format!( + "argv=<--config=release><--config=ci><--test_strategy=standalone><--nocache_test_results><--test_output=all><--test_env=TRACEDECAY_BENCHMARK_REPO_ROOT={}><--test_arg=--bench><--test_arg=--run>", + repository_root().display() ), - "{platform} cargo receipt: {receipt}" - ); - assert!( - receipt.contains("HOME=<"), - "{platform} cargo receipt: {receipt}" - ); - assert!( - !receipt.contains(&format!("HOME=<{}>", invocation.parent_home.display())), - "{platform} runner must isolate HOME: {receipt}" - ); - assert!( - receipt.contains("TRACEDECAY_DATA_DIR=<"), - "{platform} cargo receipt: {receipt}" - ); - assert!( - !receipt.contains(&format!( - "TRACEDECAY_DATA_DIR=<{}>", - invocation.parent_data.display() - )), - "{platform} runner must isolate TRACEDECAY_DATA_DIR: {receipt}" + "{platform} Bazel receipt: {receipt}" ); } } From 70c9df8de4c1651c35444eb57ac1e9c70d064ec2 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 22:22:50 -0700 Subject: [PATCH 26/29] fix(build): preserve explicit benchmark execution semantics --- AGENTS.md | 4 +++- crates/tracedecay-application/BUILD.bazel | 7 +++++++ crates/tracedecay-code-index/BUILD.bazel | 13 +++++++++++++ crates/tracedecay-global-db/BUILD.bazel | 9 +++++++++ crates/tracedecay-graph-db/BUILD.bazel | 11 +++++++++++ crates/tracedecay-query/BUILD.bazel | 7 +++++++ crates/tracedecay-runtime-core/BUILD.bazel | 8 ++++++++ crates/tracedecay-sessions/BUILD.bazel | 8 ++++++++ crates/tracedecay/BUILD.bazel | 11 +++++++++++ scripts/bazel/gen_builds.py | 16 ++++++++++++++++ 10 files changed, 93 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 50826b2fa8..67e3d93144 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,7 +79,9 @@ unauthorized external action after completing independent, authorized work. - Bazel is the sole workspace build, test, check, run, benchmark, and code-generation authority. Run `bazel build //...`, `bazel test //... --test_output=errors`, `bazel run //crates/...`, and - `bazel test //... --test_arg=--bench` for benchmarks. Edition 2024, + `bazel test //crates/: --test_arg=--bench` for an explicit + benchmark target. Benchmarks with Cargo `test = false` remain compiled by + default builds but run only when selected explicitly. Edition 2024, resolver 3, and the toolchain pinned in `rust-toolchain.toml` remain canonical inputs. Never invoke `cargo` directly; route every build, test, check, run, bench, and codegen through Bazel. diff --git a/crates/tracedecay-application/BUILD.bazel b/crates/tracedecay-application/BUILD.bazel index 9be3fff0c5..93bc090870 100644 --- a/crates/tracedecay-application/BUILD.bazel +++ b/crates/tracedecay-application/BUILD.bazel @@ -108,6 +108,7 @@ rust_test( rust_test( name = "work_rollup", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]) + glob(["tests/application_suite/**/*.rs", "tests/application_suite/github_stack_coordinator/**/*.rs"]), crate_root = "benches/work_rollup.rs", @@ -122,6 +123,12 @@ rust_test( data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "benchmark_binaries", + testonly = True, + srcs = [":work_rollup__binary"], +) + rust_test( name = "unit_test", crate = ":tracedecay-application__+test-helpers_+test-transport__dev", diff --git a/crates/tracedecay-code-index/BUILD.bazel b/crates/tracedecay-code-index/BUILD.bazel index 1b7b317943..6cbb689054 100644 --- a/crates/tracedecay-code-index/BUILD.bazel +++ b/crates/tracedecay-code-index/BUILD.bazel @@ -159,6 +159,7 @@ rust_test( rust_test( name = "code_index_chunks", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/code_index_chunks.rs", @@ -175,6 +176,7 @@ rust_test( rust_test( name = "git_ancestry", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/git_ancestry.rs", @@ -191,6 +193,7 @@ rust_test( rust_test( name = "partitioned_codec", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/partitioned_codec.rs", @@ -207,6 +210,7 @@ rust_test( rust_test( name = "restore_generation", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/restore_generation.rs", @@ -223,6 +227,7 @@ rust_test( rust_test( name = "retained_parse", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/retained_parse.rs", @@ -239,6 +244,7 @@ rust_test( rust_test( name = "sealed_storage", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/sealed_storage.rs", @@ -255,6 +261,7 @@ rust_test( rust_test( name = "source_search", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/source_search.rs", @@ -269,6 +276,12 @@ rust_test( data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "benchmark_binaries", + testonly = True, + srcs = [":code_index_chunks__binary", ":git_ancestry__binary", ":partitioned_codec__binary", ":restore_generation__binary", ":retained_parse__binary", ":sealed_storage__binary", ":source_search__binary"], +) + rust_test( name = "unit_test", crate = ":tracedecay-code-index__+eval-helpers_+lang-dockerfile_+test-helpers__dev", diff --git a/crates/tracedecay-global-db/BUILD.bazel b/crates/tracedecay-global-db/BUILD.bazel index 711c005706..a9260dbb67 100644 --- a/crates/tracedecay-global-db/BUILD.bazel +++ b/crates/tracedecay-global-db/BUILD.bazel @@ -125,6 +125,7 @@ rust_test( rust_test( name = "lcm_expansion", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/lcm_expansion.rs", @@ -141,6 +142,7 @@ rust_test( rust_test( name = "session_activity_reads", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/session_activity_reads.rs", @@ -157,6 +159,7 @@ rust_test( rust_test( name = "stack_delivery_batch", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/stack_delivery_batch.rs", @@ -171,6 +174,12 @@ rust_test( data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "benchmark_binaries", + testonly = True, + srcs = [":lcm_expansion__binary", ":session_activity_reads__binary", ":stack_delivery_batch__binary"], +) + rust_test( name = "unit_test", crate = ":tracedecay-global-db__+test-helpers__dev", diff --git a/crates/tracedecay-graph-db/BUILD.bazel b/crates/tracedecay-graph-db/BUILD.bazel index f462978ce0..39ee20c0ac 100644 --- a/crates/tracedecay-graph-db/BUILD.bazel +++ b/crates/tracedecay-graph-db/BUILD.bazel @@ -166,6 +166,7 @@ rust_test( rust_test( name = "code_traversal", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/code_traversal.rs", @@ -182,6 +183,7 @@ rust_test( rust_test( name = "mixed_read_write", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/mixed_read_write.rs", @@ -198,6 +200,7 @@ rust_test( rust_test( name = "native_state_100k", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/native_state_100k.rs", @@ -214,6 +217,7 @@ rust_test( rust_test( name = "publication_scale", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/publication_scale.rs", @@ -230,6 +234,7 @@ rust_test( rust_test( name = "replay_after_mismatch", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/replay_after_mismatch.rs", @@ -244,6 +249,12 @@ rust_test( data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "benchmark_binaries", + testonly = True, + srcs = [":code_traversal__binary", ":mixed_read_write__binary", ":native_state_100k__binary", ":publication_scale__binary", ":replay_after_mismatch__binary"], +) + rust_test( name = "unit_test", crate = ":tracedecay-graph-db__+eval-helpers_+test-helpers__dev", diff --git a/crates/tracedecay-query/BUILD.bazel b/crates/tracedecay-query/BUILD.bazel index bc7860ad73..b0cb4db3fc 100644 --- a/crates/tracedecay-query/BUILD.bazel +++ b/crates/tracedecay-query/BUILD.bazel @@ -257,6 +257,7 @@ rust_test( rust_test( name = "code_lexical_catchup", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/code_lexical_catchup.rs", @@ -271,6 +272,12 @@ rust_test( data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "benchmark_binaries", + testonly = True, + srcs = [":code_lexical_catchup__binary"], +) + rust_test( name = "unit_test", crate = ":tracedecay-query__+production_+test-helpers__dev", diff --git a/crates/tracedecay-runtime-core/BUILD.bazel b/crates/tracedecay-runtime-core/BUILD.bazel index 05133d10e0..92e9855fd7 100644 --- a/crates/tracedecay-runtime-core/BUILD.bazel +++ b/crates/tracedecay-runtime-core/BUILD.bazel @@ -136,6 +136,7 @@ rust_test( rust_test( name = "exact_sql_writer_workload", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/exact_sql_writer_workload.rs", @@ -152,6 +153,7 @@ rust_test( rust_test( name = "git_repository_authority_bin", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/git_repository_authority.rs", @@ -166,6 +168,12 @@ rust_test( data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "benchmark_binaries", + testonly = True, + srcs = [":exact_sql_writer_workload__binary", ":git_repository_authority_bin__binary"], +) + rust_test( name = "unit_test", crate = ":tracedecay-runtime-core__+test-helpers_+test-transport__dev", diff --git a/crates/tracedecay-sessions/BUILD.bazel b/crates/tracedecay-sessions/BUILD.bazel index 238c1fba10..2f971e4912 100644 --- a/crates/tracedecay-sessions/BUILD.bazel +++ b/crates/tracedecay-sessions/BUILD.bazel @@ -61,6 +61,7 @@ rust_library( rust_test( name = "cursor_dispatch_model", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/cursor_dispatch_model.rs", @@ -77,6 +78,7 @@ rust_test( rust_test( name = "host_transcript_io", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/host_transcript_io.rs", @@ -91,6 +93,12 @@ rust_test( data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "benchmark_binaries", + testonly = True, + srcs = [":cursor_dispatch_model__binary", ":host_transcript_io__binary"], +) + rust_test( name = "unit_test", crate = ":tracedecay-sessions__+test-helpers__dev", diff --git a/crates/tracedecay/BUILD.bazel b/crates/tracedecay/BUILD.bazel index 4b063d7ec9..0eb5a502a8 100644 --- a/crates/tracedecay/BUILD.bazel +++ b/crates/tracedecay/BUILD.bazel @@ -394,6 +394,7 @@ rust_test( rust_test( name = "large_repos", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/large_repos.rs", @@ -410,6 +411,7 @@ rust_test( rust_test( name = "session_temporal", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/session_temporal.rs", @@ -426,6 +428,7 @@ rust_test( rust_test( name = "transcript_ingest", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/transcript_ingest.rs", @@ -442,6 +445,7 @@ rust_test( rust_test( name = "rmcp_connection_pipeline", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/rmcp_connection_pipeline.rs", @@ -458,6 +462,7 @@ rust_test( rust_test( name = "mcp_server_construction", + tags = ["manual"], use_libtest_harness = False, srcs = glob(["benches/**/*.rs"]), crate_root = "benches/mcp_server_construction.rs", @@ -472,6 +477,12 @@ rust_test( data = glob(["benches/**", "assets/**", "fixtures/**", "data/**", "resources/**", "vendor/**", "tests/**"], allow_empty = True) + ["//tests:fixtures"], ) +filegroup( + name = "benchmark_binaries", + testonly = True, + srcs = [":large_repos__binary", ":session_temporal__binary", ":transcript_ingest__binary", ":rmcp_connection_pipeline__binary", ":mcp_server_construction__binary"], +) + rust_test( name = "unit_test", size = "large", diff --git a/scripts/bazel/gen_builds.py b/scripts/bazel/gen_builds.py index 5dab619564..10ffeb9eff 100755 --- a/scripts/bazel/gen_builds.py +++ b/scripts/bazel/gen_builds.py @@ -1100,6 +1100,7 @@ def unique_name(want): ] out = [l for l in out if l is not None] + benchmark_binaries = [] for t in p["targets"]: kinds = t["kind"] if not ({"test", "bench"} & set(kinds)): @@ -1182,10 +1183,16 @@ def unique_name(want): runfiles_env.setdefault(variable, target) env_attr = " rustc_env = " + env_dict(rustc_env) + "," if rustc_env else None test_name = unique_name(t["name"]) + manual_benchmark = "bench" in kinds and not t["test"] + if manual_benchmark: + benchmark_binaries.append(f":{test_name}__binary") out += [ "rust_test(", f' name = "{test_name}",', test_size_attr(test_name), + # Cargo metadata distinguishes opt-in measurements from default tests. + ' tags = ["manual"],' + if manual_benchmark else None, " use_libtest_harness = False," if not declared_target.get("harness", True) else None, " srcs = " + srcs + ",", @@ -1207,6 +1214,15 @@ def unique_name(want): ] out = [l for l in out if l is not None] + if benchmark_binaries: + out += [ + "filegroup(", + ' name = "benchmark_binaries",', + " testonly = True,", + " srcs = [" + q(benchmark_binaries) + "],", + ")\n", + ] + if lib_t: lib_runfiles_env = runtime_binaries(p, lib_t, lib_pulled) dev_edges = md["normal"] + md["dev"] From cc5fa03ffde39d2eeda22babd6c35af80ac9e560 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 22:34:00 -0700 Subject: [PATCH 27/29] fix(bench): mount graph owners and publish temporal relations --- .../benches/support/git.rs | 63 ++++++++++++++++--- .../benches/lcm_expansion.rs | 29 ++++++--- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/crates/tracedecay-code-index/benches/support/git.rs b/crates/tracedecay-code-index/benches/support/git.rs index e3314cf530..32554117e7 100644 --- a/crates/tracedecay-code-index/benches/support/git.rs +++ b/crates/tracedecay-code-index/benches/support/git.rs @@ -7,8 +7,9 @@ use sha2::{Digest, Sha256}; use tempfile::TempDir; use tracedecay_domain::UtcMicros; use tracedecay_graph_db::{ - GraphDbRegistration, GraphDbRegistry, GraphDbRegistryConfig, GraphGenerationManifest, - GraphIdempotencyKey, NeverCancelled, VerifiedGraphSnapshot, + GraphDbOwnerAttachmentV1, GraphDbOwnerRegistrationV1, GraphDbRegistration, GraphDbRegistry, + GraphDbRegistryConfig, GraphGenerationManifest, GraphIdempotencyKey, NeverCancelled, + VerifiedGraphSnapshot, }; use tracedecay_rusqlite_runtime::{ ExistingWriterLocator, PersistentWriter, StorageOperationExecutor, @@ -19,13 +20,15 @@ use tracedecay_rusqlite_runtime::{ use tracedecay_store::{ AdmissionConfigV1, BrainId, GraphProjectionIdentityV1, GraphPublicationInputDigestV1, GraphPublicationOperationContextV1, GraphPublicationStoreV1, ProjectId, - RetainedGraphStoreLeaseV1, RuntimeCancellationIdV1, RuntimeCancellationIdentityV1, - RuntimeDeadlineIdV1, RuntimeDeadlineV1, RuntimeInterruptionV1, RuntimeRequestControlV1, - RuntimeRequestProbeV1, StoreAuthorityEpochV1, StoreIncarnationV1, StoreRuntimeBindingV1, - StoreShardIdV1, UserProfileId, VerifiedStoreLocatorV1, canonical_store_locator_digest, + RetainedGraphStoreLeaseV1, RetainedGraphStoreOwnerAttachmentV1, + RetainedGraphStoreOwnerOperationLeaseErrorV1, RuntimeCancellationIdV1, + RuntimeCancellationIdentityV1, RuntimeDeadlineIdV1, RuntimeDeadlineV1, RuntimeInterruptionV1, + RuntimeRequestControlV1, RuntimeRequestProbeV1, StoreAuthorityEpochV1, StoreIncarnationV1, + StoreRuntimeBindingV1, StoreShardIdV1, UserProfileId, VerifiedStoreLocatorV1, + canonical_store_locator_digest, }; -#[derive(Debug)] +#[derive(Clone, Debug)] struct BenchmarkGraphLease { binding: StoreRuntimeBindingV1, verified_locator: VerifiedStoreLocatorV1, @@ -46,6 +49,27 @@ impl RetainedGraphStoreLeaseV1 for BenchmarkGraphLease { } } +impl RetainedGraphStoreOwnerAttachmentV1 for BenchmarkGraphLease { + fn binding(&self) -> &StoreRuntimeBindingV1 { + &self.binding + } + + fn verified_locator(&self) -> &VerifiedStoreLocatorV1 { + &self.verified_locator + } + + fn canonical_path(&self) -> &Path { + &self.canonical_path + } + + fn issue_operation_lease( + &self, + ) -> Result, RetainedGraphStoreOwnerOperationLeaseErrorV1> + { + Ok(Arc::new(self.clone())) + } +} + struct NoWrites; impl StorageOperationExecutor for NoWrites { @@ -130,6 +154,7 @@ pub struct PersistentGitGraph { _writer: PersistentWriter, _readers: ReaderPool, registry: GraphDbRegistry, + owner_attachment: Option, binding: StoreRuntimeBindingV1, graph_path: PathBuf, authority: GraphPublicationExactSqlStorage, @@ -197,6 +222,7 @@ impl PersistentGitGraph { Self { registry: GraphDbRegistry::new(GraphDbRegistryConfig { max_open: 1 }) .expect("benchmark graph registry config is valid"), + owner_attachment: None, authority: GraphPublicationExactSqlStorage::from_authorized_handle(handle) .expect("benchmark graph publication authority attaches"), binding, @@ -211,6 +237,7 @@ impl PersistentGitGraph { } pub fn publish(&mut self, manifest: GraphGenerationManifest) -> VerifiedGraphSnapshot { + self.mount_owner(); self.sequence += 1; let (append_control, append_probe) = operation_control(self.sequence); let append_context = @@ -259,6 +286,7 @@ impl PersistentGitGraph { } pub fn recover_snapshot(&mut self) -> VerifiedGraphSnapshot { + self.owner_attachment.take(); let registration = self.registration(); assert!( self.registry @@ -266,6 +294,7 @@ impl PersistentGitGraph { .expect("benchmark graph store closes"), "benchmark recovery must close an open graph store", ); + self.mount_owner(); self.sequence += 1; let (control, probe) = operation_control(self.sequence); let context = GraphPublicationOperationContextV1::new(&control, &probe) @@ -284,6 +313,26 @@ impl PersistentGitGraph { .expect("benchmark verified snapshot recovers") } + fn mount_owner(&mut self) { + if self.owner_attachment.is_some() { + return; + } + let operation = self.registration(); + let authority_attachment = Box::new(BenchmarkGraphLease { + binding: operation.authority_lease.binding().clone(), + verified_locator: operation.authority_lease.verified_locator().clone(), + canonical_path: operation.authority_lease.canonical_path().to_path_buf(), + }); + self.owner_attachment = Some( + self.registry + .resolve_owner_attachment(GraphDbOwnerRegistrationV1 { + operation, + authority_attachment, + }) + .expect("benchmark graph map owner mounts"), + ); + } + fn registration(&self) -> GraphDbRegistration { let canonical_path = self.graph_path.clone(); let verified_locator = VerifiedStoreLocatorV1::new( diff --git a/crates/tracedecay-global-db/benches/lcm_expansion.rs b/crates/tracedecay-global-db/benches/lcm_expansion.rs index f7a63cbb2d..fca3a34a82 100644 --- a/crates/tracedecay-global-db/benches/lcm_expansion.rs +++ b/crates/tracedecay-global-db/benches/lcm_expansion.rs @@ -17,9 +17,12 @@ use tracedecay_lcm::{ LcmContentSlice, LcmExpandRequest, LcmExpandResponse, LcmExpandTarget, LcmSourceRef, LcmSummaryNodeDraft, }; -use tracedecay_session_temporal_store::RegisteredGlobalDbSessionTemporalExecution; +use tracedecay_session_temporal_store::{ + RegisteredGlobalDbSessionTemporalExecution, SessionTemporalStore, +}; use tracedecay_store::{ AnchoredObservationWrite, ObservationProjectionStore, ObservationStore, ObservationWrite, + SessionRetrievalStore, SessionTemporalSnapshotRequestV1, build_observation_resolution_authorization_v1, build_observation_retrieval_anchor, }; use tracedecay_temporal_query::execution::{BindingDigest, ExecutionControl}; @@ -261,12 +264,20 @@ async fn build_fixture() -> Fixture { .expect("publish benchmark leaf through production authority"); expected_source_content.push(leaf_summary_text(index)); } - let root_receipt = database + database .lcm_publish_immutable_summary_guarded(root_publication(), &publication_control, || Ok(())) .await .expect("publish benchmark root through production authority"); - let generation = u64::try_from(root_receipt.generation) - .expect("published benchmark generation is non-negative"); + let temporal = SessionTemporalStore::new(database); + temporal + .materialize_pending_session_refresh_for_test(&session_id) + .await + .expect("publish benchmark temporal rows and summary relations"); + let frozen = temporal + .freeze_session_temporal_snapshot(SessionTemporalSnapshotRequestV1::new(session_id.clone())) + .await + .expect("freeze benchmark canonical temporal watermarks"); + let watermarks = frozen.watermarks(); let snapshot = TemporalExecutionSnapshot::new_authorized( TemporalSnapshotRequest::new( session_id, @@ -278,11 +289,11 @@ async fn build_fixture() -> Fixture { ) .expect("benchmark temporal request is valid"), TemporalWatermarks { - generation, - source: 0, - projection: 0, - index: 0, - summary: generation, + generation: watermarks.active_generation().value(), + source: watermarks.source_frontier(), + projection: watermarks.projection_frontier(), + index: watermarks.projection_frontier(), + summary: watermarks.summary_frontier(), }, KernelVersions { schema: 1, From e3051538569a43b88dea66ab45db988f65335742 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 23:28:30 -0700 Subject: [PATCH 28/29] test(work): interleave history read scaling measurements --- .../tests/work_product_read_scaling.rs | 71 +++++++++++-------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/crates/tracedecay-rusqlite-runtime/tests/work_product_read_scaling.rs b/crates/tracedecay-rusqlite-runtime/tests/work_product_read_scaling.rs index d1f09bb18d..f02528946b 100644 --- a/crates/tracedecay-rusqlite-runtime/tests/work_product_read_scaling.rs +++ b/crates/tracedecay-rusqlite-runtime/tests/work_product_read_scaling.rs @@ -1,4 +1,4 @@ -//! Current-head read scaling runs alone so other storage tests cannot skew its ratio. +//! Compare current-head reads against short and long published histories. #[path = "support/work_product_graph.rs"] mod work_product_graph; @@ -6,15 +6,13 @@ mod work_product_graph; pub mod work_registered_store; use tracedecay_contracts::{AddWorkTaskRequestV1, WorkGraphReadV1, WorkProductExpectedAuthorityV1}; -use tracedecay_domain::UtcMicros; +use tracedecay_domain::{UtcMicros, WorkGraphVersionV1}; use work_product_graph::{ binding, context, create, item, mutation, mutations, read_current, repository_selection, }; use work_registered_store::RegisteredWorkStore; -/// Publish `versions` graph versions, one task per version, and return the -/// fastest of several current reads of the head. -fn fastest_current_read_after(versions: u64) -> std::time::Duration { +fn published_history(versions: u64) -> (RegisteredWorkStore, WorkGraphVersionV1) { let store = RegisteredWorkStore::start("work-product-long-history"); let mut head = create( &store, @@ -43,27 +41,24 @@ fn fastest_current_read_after(versions: u64) -> std::time::Duration { ) .expect("publish the next graph version"); } - (0..5) - .map(|_| { - let started = std::time::Instant::now(); - let WorkGraphReadV1::Current { snapshot, .. } = - read_current(&store).expect("read the current head") - else { - panic!("a current read must answer with a current snapshot"); - }; - let elapsed = started.elapsed(); - assert_eq!( - snapshot.graph().version(), - head.verified_graph_version().graph_version() - ); - assert_eq!( - snapshot.graph().items().len(), - usize::try_from(versions).expect("versions fit") - ); - elapsed - }) - .min() - .expect("at least one read") + let version = head.verified_graph_version().graph_version(); + (store, version) +} + +fn current_read(store: &RegisteredWorkStore, version: WorkGraphVersionV1) -> std::time::Duration { + let started = std::time::Instant::now(); + let WorkGraphReadV1::Current { snapshot, .. } = + read_current(store).expect("read the current head") + else { + panic!("a current read must answer with a current snapshot"); + }; + let elapsed = started.elapsed(); + assert_eq!(snapshot.graph().version(), version); + assert_eq!( + snapshot.graph().items().len(), + usize::try_from(version.get()).expect("versions fit") + ); + elapsed } /// Every Work mutation reads its current head first, so a current read that @@ -75,9 +70,27 @@ fn fastest_current_read_after(versions: u64) -> std::time::Duration { /// about 136x, so 48x separates them by roughly threefold either way. #[test] fn a_current_read_folds_a_long_history_once_rather_than_once_per_version() { - let short = fastest_current_read_after(16); - let long = fastest_current_read_after(128); - let growth = long.as_secs_f64() / short.as_secs_f64(); + let (short_store, short_version) = published_history(16); + let (long_store, long_version) = published_history(128); + current_read(&short_store, short_version); + current_read(&long_store, long_version); + + // Prepare both histories before timing so publication and changing compiler + // load do not separate the short and long measurement windows. + let mut samples: Vec<_> = (0..5) + .map(|sample| { + let (short, long) = if sample % 2 == 0 { + let short = current_read(&short_store, short_version); + (short, current_read(&long_store, long_version)) + } else { + let long = current_read(&long_store, long_version); + (current_read(&short_store, short_version), long) + }; + (long.as_secs_f64() / short.as_secs_f64(), short, long) + }) + .collect(); + samples.sort_by(|left, right| left.0.total_cmp(&right.0)); + let (growth, short, long) = samples[samples.len() / 2]; assert!( growth < 48.0, "an 8x longer history made a current read {growth:.0}x slower \ From ecb8d24ad0347d1a6fc732c30dfdcd5758930d47 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 8 Oct 2026 23:39:12 -0700 Subject: [PATCH 29/29] test(diagnostics): fence proof renewal during stale reads --- .../src/daemon/production_harness.rs | 25 +++++++++++++++++++ .../tests/mcp_suite/diagnostics_read_test.rs | 21 +++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/crates/tracedecay/src/daemon/production_harness.rs b/crates/tracedecay/src/daemon/production_harness.rs index 44e875834d..47e4f4aabd 100644 --- a/crates/tracedecay/src/daemon/production_harness.rs +++ b/crates/tracedecay/src/daemon/production_harness.rs @@ -21,6 +21,8 @@ use super::*; #[cfg(unix)] use tracedecay_application::pr_tracking::acquire_manual_branch_lifecycle; use tracedecay_code_index_runtime::CodeIndexSchedulerRegistryV1; +#[cfg(feature = "test-helpers")] +use tracedecay_code_index_runtime::code_index_scheduler::CodeIndexWorktreeSchedulerV1; #[cfg(all(unix, feature = "test-transport"))] use tracedecay_code_index_runtime::git_transactions; use tracedecay_daemon_identity::profile_identity; @@ -888,6 +890,29 @@ impl ProductionProjectCompositionHarnessV1 { .await) } + /// Uses the registry's existing test handle to fence source-proof renewal + /// while a production read observes the previous publication. + #[cfg(feature = "test-helpers")] + pub async fn project_scheduler_handle( + &self, + project_root: &Path, + ) -> Result>> { + let resources = self + .resources + .as_ref() + .ok_or_else(|| TraceDecayError::Config { + message: "production-composition harness is shut down".to_owned(), + })?; + resources + .invocation + .code_index_schedulers + .scheduler_handle(project_root) + .await + .ok_or_else(|| TraceDecayError::Config { + message: "production-composition project scheduler is not mounted".to_owned(), + }) + } + pub async fn project_data_root(&self, project_root: impl AsRef) -> Result { Ok(self .server(project_root)? diff --git a/crates/tracedecay/tests/mcp_suite/diagnostics_read_test.rs b/crates/tracedecay/tests/mcp_suite/diagnostics_read_test.rs index 6621a80664..ac6dd468d3 100644 --- a/crates/tracedecay/tests/mcp_suite/diagnostics_read_test.rs +++ b/crates/tracedecay/tests/mcp_suite/diagnostics_read_test.rs @@ -193,6 +193,20 @@ async fn moved_source_proof_reads_the_publication_as_stale_until_renewed() { let published = await_published_diagnostics(&server, arguments.clone()).await; assert_eq!(published_records(&published).len(), 1, "{published}"); + let scheduler = fixture + .harness + .project_scheduler_handle(&fixture.project_root) + .await + .expect("mounted project scheduler"); + let (held_tx, held_rx) = tokio::sync::oneshot::channel(); + let (release_tx, release_rx) = tokio::sync::oneshot::channel::<()>(); + let holding = tokio::task::spawn_blocking(move || { + let _guard = scheduler.lock().expect("hold source-proof renewal"); + held_tx.send(()).expect("scheduler held"); + let _ = release_rx.blocking_recv(); + }); + held_rx.await.expect("source-proof renewal is fenced"); + let index = std::fs::File::options() .write(true) .open(fixture.project_root.join(".git/index")) @@ -202,7 +216,12 @@ async fn moved_source_proof_reads_the_publication_as_stale_until_renewed() { .expect("move the git index mtime"); drop(index); - let mut codes = Vec::new(); + let stale = + handle_real_server_tool_call(&server, "tracedecay_diagnostics", arguments.clone()).await; + drop(release_tx); + holding.await.expect("release source-proof renewal"); + assert_eq!(stale["isError"], json!(true), "{stale}"); + let mut codes = vec![stale["structuredContent"]["problem"]["code"].clone()]; let renewed = loop { let result = handle_real_server_tool_call(&server, "tracedecay_diagnostics", arguments.clone())