From 08f52e75b1c4e0fb64081e59a111b895e8d7f5c3 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:19:37 -0800 Subject: [PATCH 01/14] perf(code-index): avoid unrelated sparse evidence reads Refs #4520 --- .../src/production/file_evidence_rows.rs | 4 ++ .../src/production/partitioned_codec.rs | 55 ++++++++++++++++++- .../src/production/sparse_increment_tests.rs | 34 ++++++++++++ .../src/production/sparse_resolution.rs | 12 +++- 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/crates/tracedecay-code-index/src/production/file_evidence_rows.rs b/crates/tracedecay-code-index/src/production/file_evidence_rows.rs index cabe28fec67..515bf951aba 100644 --- a/crates/tracedecay-code-index/src/production/file_evidence_rows.rs +++ b/crates/tracedecay-code-index/src/production/file_evidence_rows.rs @@ -623,6 +623,10 @@ impl PersistedFileEvidenceV1 { .any(|path| paths.contains(path.as_str())) } + pub(super) fn target_paths(&self) -> impl Iterator { + self.target_paths.iter().map(String::as_str) + } + pub(super) fn cross_file_edge_count(&self) -> usize { self.edges.len() } diff --git a/crates/tracedecay-code-index/src/production/partitioned_codec.rs b/crates/tracedecay-code-index/src/production/partitioned_codec.rs index e0485899463..cc7c9d82e8f 100644 --- a/crates/tracedecay-code-index/src/production/partitioned_codec.rs +++ b/crates/tracedecay-code-index/src/production/partitioned_codec.rs @@ -142,6 +142,53 @@ pub(super) struct PartitionedFileEvidenceDescriptorV1 { /// that carries the file knows to reseal it with identity lineage. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub(super) explicit_lineage: bool, + /// Negative edge-target filter; absent on an older seal means read it. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(super) target_filter: Option, +} + +/// Four independent path-hash probes. False positives decode evidence as +/// before; false negatives would suppress required incoming-edge repoints. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub(super) struct EvidenceTargetFilterV1([u64; 4]); + +impl EvidenceTargetFilterV1 { + fn path_bits(path: &str) -> [usize; 4] { + let digest = Sha256::digest(path.as_bytes()); + std::array::from_fn(|probe| { + usize::from(u16::from_be_bytes([digest[probe * 2], digest[probe * 2 + 1]])) + % 256 + }) + } + + fn from_paths<'a>(paths: impl Iterator) -> Self { + let mut bits = [0_u64; 4]; + for path in paths { + for bit in Self::path_bits(path) { + bits[bit / 64] |= 1_u64 << (bit % 64); + } + } + Self(bits) + } + + fn might_contain(&self, path: &str) -> bool { + Self::path_bits(path) + .into_iter() + .all(|bit| self.0[bit / 64] & (1_u64 << (bit % 64)) != 0) + } +} + +impl PartitionedFileEvidenceDescriptorV1 { + pub(super) fn might_target_any<'a>( + &self, + mut paths: impl Iterator, + ) -> bool { + match &self.target_filter { + Some(filter) => paths.any(|path| filter.might_contain(path)), + None => true, + } + } } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -1724,6 +1771,7 @@ pub(super) fn encode_file_evidence_segment_charged( })?, decoded_size_bytes, explicit_lineage: evidence.has_explicit_lineage(), + target_filter: Some(EvidenceTargetFilterV1::from_paths(evidence.target_paths())), }; Ok((descriptor, bytes)) } @@ -1748,7 +1796,12 @@ pub(super) fn decode_file_evidence_segment( "sealed file evidence decoding failed: {error}" )) })?; - if evidence.has_explicit_lineage() != descriptor.explicit_lineage || evidence.is_empty() { + if evidence.has_explicit_lineage() != descriptor.explicit_lineage + || evidence.is_empty() + || descriptor.target_filter.as_ref().is_some_and(|filter| { + *filter != EvidenceTargetFilterV1::from_paths(evidence.target_paths()) + }) + { return Err(CodeIndexProductionErrorV1::Contract( "sealed file evidence does not match its descriptor".to_owned(), )); diff --git a/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs b/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs index 01eb1dcb764..73eff7e683f 100644 --- a/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs +++ b/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs @@ -343,6 +343,7 @@ fn a_comment_beside_same_named_symbols_decodes_only_what_it_reaches() { callers: &'a [(String, String)], ) -> Vec<(&'a str, &'a str, &'a str)> { let mut tree = rust_tree(util, MAIN); + tree[0].2 = "pub mod main;\npub mod util;\npub mod other;\n"; tree.push(("src/other.rs", RUST, other)); tree.extend( callers @@ -388,6 +389,28 @@ fn a_comment_beside_same_named_symbols_decodes_only_what_it_reaches() { .iter() .map(|(_, digest)| store.segment_reads(digest)) .collect::>(); + let evidence_of = |path: &str| { + let key = snapshot + .iter() + .position(|file| file.logical_path == path) + .expect("parent evidence file"); + manifest + .file_evidence + .iter() + .find(|descriptor| descriptor.file_key as usize == key) + .map(|descriptor| descriptor.segment_digest.clone()) + .expect("a resolved cross-file call has sealed evidence") + }; + let unrelated_evidence = callers + .iter() + .map(|(path, _)| (path.as_str(), evidence_of(path))) + .collect::>(); + let evidence_reads_before = unrelated_evidence + .iter() + .map(|(_, digest)| store.segment_reads(digest)) + .collect::>(); + let dependent_evidence = evidence_of("src/main.rs"); + let dependent_reads_before = store.segment_reads(&dependent_evidence); let sparse = publish( &mut incremental, @@ -406,6 +429,17 @@ fn a_comment_beside_same_named_symbols_decodes_only_what_it_reaches() { "{path} names `new` but binds nowhere in the edited file, so its segment stays sealed" ); } + for ((path, digest), before) in unrelated_evidence.iter().zip(evidence_reads_before) { + assert_eq!( + store.segment_reads(digest), + before, + "{path} has unrelated sealed edges and must not decode its evidence" + ); + } + assert!( + store.segment_reads(&dependent_evidence) > dependent_reads_before, + "a caller whose sealed edge lands in the edited file must still be repointed" + ); assert_sparse_matches_cold( &caller_tree(&util, other, &callers), &caller_tree(&edited, other, &callers), diff --git a/crates/tracedecay-code-index/src/production/sparse_resolution.rs b/crates/tracedecay-code-index/src/production/sparse_resolution.rs index 5db6040d8d7..6378d8b34ec 100644 --- a/crates/tracedecay-code-index/src/production/sparse_resolution.rs +++ b/crates/tracedecay-code-index/src/production/sparse_resolution.rs @@ -711,9 +711,15 @@ pub(super) fn resolve_edit( let parent_key = parent_key_of_path .get(files[file_index].logical_path()) .ok_or_else(|| contract("a carried file has no parent segment"))?; - let repoints = parent - .file_evidence(*parent_key)? - .is_some_and(|evidence| evidence.targets_any(&edited_paths)); + let might_repoint = parent + .file_evidence_descriptor(*parent_key) + .is_some_and(|descriptor| { + descriptor.might_target_any(edited_paths.iter().copied()) + }); + let repoints = might_repoint + && parent + .file_evidence(*parent_key)? + .is_some_and(|evidence| evidence.targets_any(&edited_paths)); if repoints { files[file_index].binding()?; } From d009fee7ce2b9a30e16f039f682030bb2e8be325 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:55:20 -0800 Subject: [PATCH 02/14] fix(code-index): require authenticated target filters in new seal Refs #4520 --- .../src/production/partitioned_codec.rs | 49 +++++++++++++------ .../src/production/sealed_codec.rs | 2 +- .../src/production/sparse_resolution.rs | 11 +++-- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/crates/tracedecay-code-index/src/production/partitioned_codec.rs b/crates/tracedecay-code-index/src/production/partitioned_codec.rs index cc7c9d82e8f..fa6c55abdcd 100644 --- a/crates/tracedecay-code-index/src/production/partitioned_codec.rs +++ b/crates/tracedecay-code-index/src/production/partitioned_codec.rs @@ -142,9 +142,9 @@ pub(super) struct PartitionedFileEvidenceDescriptorV1 { /// that carries the file knows to reseal it with identity lineage. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub(super) explicit_lineage: bool, - /// Negative edge-target filter; absent on an older seal means read it. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub(super) target_filter: Option, + /// Negative edge-target filter authenticated against this segment at restore. + /// A missing filter is a format violation, never a reason to omit an edge. + pub(super) target_filter: EvidenceTargetFilterV1, } /// Four independent path-hash probes. False positives decode evidence as @@ -157,8 +157,10 @@ impl EvidenceTargetFilterV1 { fn path_bits(path: &str) -> [usize; 4] { let digest = Sha256::digest(path.as_bytes()); std::array::from_fn(|probe| { - usize::from(u16::from_be_bytes([digest[probe * 2], digest[probe * 2 + 1]])) - % 256 + usize::from(u16::from_be_bytes([ + digest[probe * 2], + digest[probe * 2 + 1], + ])) % 256 }) } @@ -180,14 +182,31 @@ impl EvidenceTargetFilterV1 { } impl PartitionedFileEvidenceDescriptorV1 { - pub(super) fn might_target_any<'a>( - &self, - mut paths: impl Iterator, - ) -> bool { - match &self.target_filter { - Some(filter) => paths.any(|path| filter.might_contain(path)), - None => true, + pub(super) fn might_target_any<'a>(&self, mut paths: impl Iterator) -> bool { + paths.any(|path| self.target_filter.might_contain(path)) + } +} + +#[cfg(test)] +mod evidence_target_filter_tests { + use super::EvidenceTargetFilterV1; + + #[test] + fn sealed_target_filter_round_trip_has_no_false_negatives() { + let paths = ["src/util.rs", "src/other.rs", "src/sub/leaf.rs"]; + let filter = EvidenceTargetFilterV1::from_paths(paths.into_iter()); + let bytes = serde_json::to_vec(&filter).expect("encode target filter"); + let restored: EvidenceTargetFilterV1 = + serde_json::from_slice(&bytes).expect("decode target filter"); + assert_eq!(restored, filter); + for path in paths { + assert!(restored.might_contain(path), "{path} must not be filtered"); } + let empty = EvidenceTargetFilterV1::from_paths(std::iter::empty()); + assert!( + paths.into_iter().all(|path| !empty.might_contain(path)), + "empty edge set must safely skip unrelated evidence" + ); } } @@ -1771,7 +1790,7 @@ pub(super) fn encode_file_evidence_segment_charged( })?, decoded_size_bytes, explicit_lineage: evidence.has_explicit_lineage(), - target_filter: Some(EvidenceTargetFilterV1::from_paths(evidence.target_paths())), + target_filter: EvidenceTargetFilterV1::from_paths(evidence.target_paths()), }; Ok((descriptor, bytes)) } @@ -1798,9 +1817,7 @@ pub(super) fn decode_file_evidence_segment( })?; if evidence.has_explicit_lineage() != descriptor.explicit_lineage || evidence.is_empty() - || descriptor.target_filter.as_ref().is_some_and(|filter| { - *filter != EvidenceTargetFilterV1::from_paths(evidence.target_paths()) - }) + || descriptor.target_filter != EvidenceTargetFilterV1::from_paths(evidence.target_paths()) { return Err(CodeIndexProductionErrorV1::Contract( "sealed file evidence does not match its descriptor".to_owned(), diff --git a/crates/tracedecay-code-index/src/production/sealed_codec.rs b/crates/tracedecay-code-index/src/production/sealed_codec.rs index e53b0d13286..265d650455b 100644 --- a/crates/tracedecay-code-index/src/production/sealed_codec.rs +++ b/crates/tracedecay-code-index/src/production/sealed_codec.rs @@ -55,7 +55,7 @@ use super::*; /// from source rather than migrated. Revisions through eight also predate /// required clone-body source rows, so the rebuild keeps them from reading as /// successful empty clone evidence. -pub const SEALED_GENERATION_FORMAT_REVISION_V1: u32 = 21; +pub const SEALED_GENERATION_FORMAT_REVISION_V1: u32 = 22; /// The typed refusal for a sealed generation this build no longer reads. pub fn superseded_sealed_generation_revision(revision: u32) -> CodeIndexProductionErrorV1 { diff --git a/crates/tracedecay-code-index/src/production/sparse_resolution.rs b/crates/tracedecay-code-index/src/production/sparse_resolution.rs index 6378d8b34ec..095e16cb105 100644 --- a/crates/tracedecay-code-index/src/production/sparse_resolution.rs +++ b/crates/tracedecay-code-index/src/production/sparse_resolution.rs @@ -711,11 +711,12 @@ pub(super) fn resolve_edit( let parent_key = parent_key_of_path .get(files[file_index].logical_path()) .ok_or_else(|| contract("a carried file has no parent segment"))?; - let might_repoint = parent - .file_evidence_descriptor(*parent_key) - .is_some_and(|descriptor| { - descriptor.might_target_any(edited_paths.iter().copied()) - }); + let might_repoint = + parent + .file_evidence_descriptor(*parent_key) + .is_some_and(|descriptor| { + descriptor.might_target_any(edited_paths.iter().copied()) + }); let repoints = might_repoint && parent .file_evidence(*parent_key)? From 52db4abb910c5e3b3447f00898ad5f98389cb5df Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 16:56:11 -0800 Subject: [PATCH 03/14] test(code-index): reject filterless current-revision evidence Refs #4520 --- .../production_orchestration.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs b/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs index 1e1558def46..a055b3c7d32 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs @@ -3458,6 +3458,34 @@ fn partitioned_codec_has_stable_bytes_and_round_trips() { !file_evidence_digests.is_empty(), "the fixture must seal file evidence" ); + // This revision requires each evidence descriptor's authenticated target + // filter. A missing filter is not a permissive legacy manifest. + let evidence_descriptors = envelope["generation"]["file_evidence"] + .as_array() + .expect("file evidence descriptors"); + for descriptor in evidence_descriptors { + assert_eq!( + descriptor["target_filter"].as_array().map(Vec::len), + Some(4), + "each sealed evidence descriptor has four target-filter words" + ); + } + let mut filterless = envelope.clone(); + filterless["generation"]["file_evidence"] + .as_array_mut() + .expect("file evidence descriptors") + .first_mut() + .expect("fixture has evidence") + .as_object_mut() + .expect("typed evidence descriptor") + .remove("target_filter") + .expect("filter must be present"); + let error = CodeIndexPublishedGenerationV1::partitioned_text_metadata(&reseal_manifest(filterless)) + .expect_err("a current-revision manifest without a target filter must be refused"); + assert!( + error.to_string().contains("missing field `target_filter`"), + "filterless manifest reached the wrong refusal: {error}" + ); // Buffer address -> its capacity after the last read it served. let mut file_buffers = BTreeMap::new(); let mut file_evidence_buffers = BTreeMap::new(); From f1c95647c983186078aed98bcfcbd4001eecb722 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:27:02 -0800 Subject: [PATCH 04/14] style(code-index): format missing-filter regression --- .../tests/code_index_suite/production_orchestration.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs b/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs index a055b3c7d32..dc225f7f9ef 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs @@ -3480,8 +3480,9 @@ fn partitioned_codec_has_stable_bytes_and_round_trips() { .expect("typed evidence descriptor") .remove("target_filter") .expect("filter must be present"); - let error = CodeIndexPublishedGenerationV1::partitioned_text_metadata(&reseal_manifest(filterless)) - .expect_err("a current-revision manifest without a target filter must be refused"); + let error = + CodeIndexPublishedGenerationV1::partitioned_text_metadata(&reseal_manifest(filterless)) + .expect_err("a current-revision manifest without a target filter must be refused"); assert!( error.to_string().contains("missing field `target_filter`"), "filterless manifest reached the wrong refusal: {error}" From c54dc475511e36c134a3b79cb8b5500baf1738de Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:48:12 -0800 Subject: [PATCH 05/14] test(code-index): verify sparse comment publication end to end Bind parent-evidence read assertions, cold-derived chunk deltas, and incoming-edge repoint checks to the same sparse successor. Require actual dependent reads, unchanged caller occurrences, positive chunk changes and reuse, and exact nonzero fan-in/fan-out. Refs #4520 --- .../tests/code_index_suite/main.rs | 1 + .../sparse_comment_publication.rs | 230 ++++++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs diff --git a/crates/tracedecay-code-index/tests/code_index_suite/main.rs b/crates/tracedecay-code-index/tests/code_index_suite/main.rs index 072d3c32a3e..59aeb59dd68 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/main.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/main.rs @@ -23,6 +23,7 @@ mod retained_parse_canonical_identity; mod sanitized_intake; mod sealed_generation_restore; mod search_chunks; +mod sparse_comment_publication; mod support; mod symbol_span_digest; mod test_attribution; diff --git a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs new file mode 100644 index 00000000000..7706a0500e6 --- /dev/null +++ b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs @@ -0,0 +1,230 @@ +//! Sparse publication must preserve the cold graph and exact chunk delta, +//! including callers whose source did not change when a target was re-keyed. + +use std::sync::Arc; + +use sha2::{Digest, Sha256}; +use tracedecay_code_index::{ + chunks::content_digest, + incremental::plan_chunk_increment, + production::{ + CodeIndexBuildRequestV1, CodeIndexCapturedFileV1, CodeIndexProductionOwnerV1, + CodeIndexPublishedGenerationV1, MemorySealedPublicationStoreV1, + }, +}; +use tracedecay_domain::{ + FileOccurrenceId, LanguageId, ManifestDigest, SanitizedCodeFileV1, SensitivityLevelV1, + SnapshotFileDispositionV1, SymbolOccurrenceId, +}; + +use crate::{ + production_orchestration::{ActiveControl, config, request_with_source}, + support::{cold_generation, id}, +}; + +fn tree_request(util: &str, sealed_at: i64, phase: &str) -> CodeIndexBuildRequestV1 { + let root = concat!( + "pub mod main;\npub mod util;\npub mod other;\npub mod sibling;\n", + "pub mod caller0;\npub mod caller1;\npub mod caller2;\npub mod caller3;\n", + ); + let mut request = request_with_source( + "file.sparse.seed", + sealed_at, + &format!("commit.sparse.{phase}"), + &format!("tree.sparse.{phase}"), + root, + ); + let mut sources = vec![ + ("src/lib.rs".to_owned(), root.to_owned()), + ( + "src/main.rs".to_owned(), + "pub fn run() -> u32 { crate::util::helper() + crate::util::extra() }\n".to_owned(), + ), + ( + "src/sibling.rs".to_owned(), + "pub fn second_caller() -> u32 { crate::util::helper() }\n".to_owned(), + ), + ("src/util.rs".to_owned(), util.to_owned()), + ( + "src/other.rs".to_owned(), + "pub struct Other;\nimpl Other { pub fn new() -> Self { Other } }\n".to_owned(), + ), + ]; + sources.extend((0..4).map(|n| { + ( + format!("src/caller{n}.rs"), + format!("pub fn caller{n}() {{ let _ = crate::other::Other::new(); }}\n"), + ) + })); + sources.sort(); + request.snapshot.files.clear(); + request.captured_files.clear(); + request.changed_files.clear(); + let mut identity = Sha256::new(); + for (path, source) in sources { + let digest = Sha256::digest([path.as_bytes(), b"\0", source.as_bytes()].concat()); + let occurrence = id::(&format!("file.sparse.{digest:x}")); + identity.update(digest); + request.snapshot.files.push(SanitizedCodeFileV1 { + file_occurrence_id: occurrence.clone(), + logical_path: path.clone(), + language: Some(id::("rust")), + content_digest: content_digest(source.as_bytes()), + disposition: SnapshotFileDispositionV1::Present, + }); + request.captured_files.push(CodeIndexCapturedFileV1 { + file_occurrence_id: occurrence, + sanitized_bytes: Arc::from(source.as_bytes()), + sensitivity_level: SensitivityLevelV1::Public, + }); + request.changed_files.insert(path); + } + request.snapshot.content_identity = content_digest(&identity.finalize()); + request +} + +fn symbol(generation: &CodeIndexPublishedGenerationV1, name: &str) -> SymbolOccurrenceId { + let matches = generation + .symbols() + .symbols + .iter() + .filter(|symbol| symbol.simple_name == name) + .map(|symbol| symbol.occurrence.clone()) + .collect::>(); + assert_eq!(matches.len(), 1, "fixture must have exactly one {name}"); + matches[0].clone() +} + +#[test] +fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta() { + let util = (0..8) + .map(|n| { + format!("pub struct Kind{n};\nimpl Kind{n} {{ pub fn new() -> Self {{ Kind{n} }} }}\n") + }) + .collect::() + + "pub fn helper() -> u32 { 1 }\npub fn extra() -> u32 { 2 }\n"; + let store = MemorySealedPublicationStoreV1::default(); + let mut owner = CodeIndexProductionOwnerV1::new(config(), store.clone()).expect("owner"); + let before_request = tree_request(&util, 1_100_000, "before"); + let parent = owner + .build_and_publish(before_request.clone(), &ActiveControl) + .expect("parent publishes"); + assert!(parent.cold_reason().is_some()); + let envelope: serde_json::Value = + serde_json::from_slice(parent.manifest_bytes()).expect("sealed manifest envelope"); + let evidence_digest = |path: &str| { + let key = before_request + .snapshot + .files + .iter() + .position(|file| file.logical_path == path) + .expect("fixture path"); + let key = u64::try_from(key).expect("file key fits u64"); + let descriptor = envelope["generation"]["file_evidence"] + .as_array() + .expect("evidence descriptors") + .iter() + .find(|descriptor| descriptor["file_key"].as_u64() == Some(key)) + .expect("fixture must seal real cross-file evidence"); + serde_json::from_value::(descriptor["segment_digest"].clone()) + .expect("evidence digest") + }; + let unrelated = (0..4) + .map(|n| evidence_digest(&format!("src/caller{n}.rs"))) + .map(|digest| { + let reads = store.segment_reads(&digest); + (digest, reads) + }) + .collect::>(); + let dependent = evidence_digest("src/main.rs"); + let dependent_reads = store.segment_reads(&dependent); + let edited = format!("{util}// comment only\n"); + let cold_request = tree_request(&edited, 1_200_000, "after"); + let mut sparse_request = cold_request.clone(); + let edited_occurrence = sparse_request + .snapshot + .files + .iter() + .find(|file| file.logical_path == "src/util.rs") + .expect("edited file") + .file_occurrence_id + .clone(); + sparse_request + .captured_files + .retain(|file| file.file_occurrence_id == edited_occurrence); + assert_eq!(sparse_request.captured_files.len(), 1); + sparse_request.changed_files = ["src/util.rs".to_owned()].into_iter().collect(); + let sparse = owner + .build_and_publish(sparse_request, &ActiveControl) + .expect("sparse successor publishes without recapturing callers"); + assert_eq!(sparse.cold_reason(), None); + + // Observe reads before restoring either result, not on a second build. + for (digest, reads) in unrelated { + assert_eq!(store.segment_reads(&digest), reads, "unrelated evidence"); + } + assert!(store.segment_reads(&dependent) > dependent_reads); + + let mut cold_owner = + CodeIndexProductionOwnerV1::new(config(), MemorySealedPublicationStoreV1::default()) + .expect("cold owner"); + let cold = cold_owner + .build_and_publish(cold_request, &ActiveControl) + .expect("cold successor publishes"); + assert!(cold.cold_reason().is_some()); + let before = cold_generation(&parent); + let after = cold_generation(&sparse); + let expected = cold_generation(&cold); + let expected_delta = plan_chunk_increment(Some(before.chunks()), expected.chunks()) + .expect("cold chunks define the complete expected delta"); + assert!(!expected_delta.added_or_changed.is_empty()); + assert!(expected_delta.reused_count > 0); + assert_eq!(sparse.projection().request().changes, expected_delta); + assert_eq!(after.edges(), expected.edges()); + + let old_helper = symbol(&before, "helper"); + let helper = symbol(&after, "helper"); + let old_other = symbol(&before, "extra"); + let other = symbol(&after, "extra"); + let run = symbol(&after, "run"); + let sibling = symbol(&after, "second_caller"); + assert_ne!(old_helper, helper, "comment edit re-keys its target"); + assert_ne!(old_other, other); + assert_eq!(run, symbol(&before, "run")); + assert_eq!(sibling, symbol(&before, "second_caller")); + for (from, to) in [(&run, &helper), (&run, &other), (&sibling, &helper)] { + assert_eq!( + after + .edges() + .iter() + .filter(|edge| &edge.from_occurrence == from && &edge.to_occurrence == to) + .count(), + 1, + "each carried caller must point to the new occurrence exactly once" + ); + } + assert!(after.edges().iter().all(|edge| { + edge.from_occurrence != old_helper + && edge.to_occurrence != old_helper + && edge.from_occurrence != old_other + && edge.to_occurrence != old_other + })); + assert_eq!( + after + .edges() + .iter() + .filter(|edge| edge.to_occurrence == helper) + .count(), + 2, + "target fan-in survives repointing" + ); + assert_eq!( + after + .edges() + .iter() + .filter(|edge| edge.from_occurrence == run) + .count(), + 2, + "untouched caller fan-out survives repointing" + ); +} From 96b71325f1317598082edcf093aab4924fcb3401 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 18:12:58 -0800 Subject: [PATCH 06/14] fix(code-index): encode test file digests explicitly Convert SHA-256 bytes to a stable file-occurrence hex prefix instead of requiring LowerHex on GenericArray; addresses hosted Rust E0277 in the sparse publication integration test. Refs #4520 --- .../tests/code_index_suite/sparse_comment_publication.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs index 7706a0500e6..2925f8e9e5b 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs @@ -63,7 +63,14 @@ fn tree_request(util: &str, sealed_at: i64, phase: &str) -> CodeIndexBuildReques let mut identity = Sha256::new(); for (path, source) in sources { let digest = Sha256::digest([path.as_bytes(), b"\0", source.as_bytes()].concat()); - let occurrence = id::(&format!("file.sparse.{digest:x}")); + let occurrence = id::(&format!( + "file.sparse.{}", + digest + .iter() + .take(12) + .map(|byte| format!("{byte:02x}")) + .collect::() + )); identity.update(digest); request.snapshot.files.push(SanitizedCodeFileV1 { file_occurrence_id: occurrence.clone(), From 7b7e179e410148a25acfe9cd4fa219d074d2d622 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 18:15:21 -0800 Subject: [PATCH 07/14] test(code-index): compare cold and sparse chunk partitions A cold build starts with generation sequence one while a sparse successor advances its parent. Compare every canonical chunk-change partition and validate the actual sealed generation identities, not the two different manifest addresses. Refs #4520 --- .../sparse_comment_publication.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs index 2925f8e9e5b..391a8c854f7 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs @@ -186,7 +186,21 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta .expect("cold chunks define the complete expected delta"); assert!(!expected_delta.added_or_changed.is_empty()); assert!(expected_delta.reused_count > 0); - assert_eq!(sparse.projection().request().changes, expected_delta); + // A cold seal starts at repository sequence one, while a real sparse + // successor advances its parent's generation sequence. Their canonical + // partitions must agree, but their seal/generation identities must not. + let changes = &sparse.projection().request().changes; + changes.validate().expect("published chunk delta is canonical"); + assert_eq!( + changes.from_generation.as_ref(), + Some(&parent.manifest().generation_id) + ); + assert_eq!(changes.to_generation, sparse.manifest().generation_id); + assert_ne!(changes.to_generation, expected_delta.to_generation); + assert_eq!(changes.added_or_changed, expected_delta.added_or_changed); + assert_eq!(changes.deleted, expected_delta.deleted); + assert_eq!(changes.reused_count, expected_delta.reused_count); + assert_eq!(changes.reused_digest, expected_delta.reused_digest); assert_eq!(after.edges(), expected.edges()); let old_helper = symbol(&before, "helper"); From 1c63f1cac22ce5476b1f4fc579be2bc25bf18f08 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 19:33:47 -0800 Subject: [PATCH 08/14] fix(code-index): restore cross-file fixture and refresh seal golden Refs #4520 Use actually resolvable module-level `new` calls in both sparse regressions; retain same-name unrelated candidates, real evidence reads, and strict positive repoint and chunk-delta assertions. Pin the revision-22 canonical state digest measured by hosted Bazel code_index_suite (run 42437748915, Linux fleet). Apply rustfmt's exact requested line wrap without changing validation. --- .../src/production/sparse_increment_tests.rs | 4 ++-- .../tests/code_index_suite/production_orchestration.rs | 2 +- .../tests/code_index_suite/sparse_comment_publication.rs | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs b/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs index 73eff7e683f..d1001bd79f5 100644 --- a/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs +++ b/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs @@ -328,12 +328,12 @@ fn a_comment_beside_same_named_symbols_decodes_only_what_it_reaches() { .collect::() + UTIL; let edited = format!("{util}// probe line\n"); - let other = "pub struct Other;\nimpl Other { pub fn new() -> Self { Other } }\n"; + let other = "pub fn new() -> u32 { 7 }\n"; let callers = (0..4) .map(|n| { ( format!("src/caller{n}.rs"), - format!("pub fn caller{n}() {{ let _ = crate::other::Other::new(); }}\n"), + format!("pub fn caller{n}() {{ let _ = crate::other::new(); }}\n"), ) }) .collect::>(); diff --git a/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs b/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs index dc225f7f9ef..855b5e553bf 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/production_orchestration.rs @@ -3171,7 +3171,7 @@ fn partitioned_codec_fixture() -> ( } const PARTITIONED_FORMAT_STATE_DIGEST: &str = - "sha256:0ba68ca337a4760c0221c1212eec669965f43abdd244f0b21be7f705f7449918"; + "sha256:08e91a89b2dd82615897c5df08b74740fc483551f8f8cef1ef57cf96a93c837f"; const PARTITIONED_FORMAT_SEGMENTS: &[(&str, u64)] = &[ ( "sha256:9b175e6aa1ddf6db4138181c0dd1cd522f37381898a58d46e26b1165b613727b", diff --git a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs index 391a8c854f7..8b705ceed6e 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs @@ -47,13 +47,13 @@ fn tree_request(util: &str, sealed_at: i64, phase: &str) -> CodeIndexBuildReques ("src/util.rs".to_owned(), util.to_owned()), ( "src/other.rs".to_owned(), - "pub struct Other;\nimpl Other { pub fn new() -> Self { Other } }\n".to_owned(), + "pub fn new() -> u32 { 7 }\n".to_owned(), ), ]; sources.extend((0..4).map(|n| { ( format!("src/caller{n}.rs"), - format!("pub fn caller{n}() {{ let _ = crate::other::Other::new(); }}\n"), + format!("pub fn caller{n}() {{ let _ = crate::other::new(); }}\n"), ) })); sources.sort(); @@ -190,7 +190,9 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta // successor advances its parent's generation sequence. Their canonical // partitions must agree, but their seal/generation identities must not. let changes = &sparse.projection().request().changes; - changes.validate().expect("published chunk delta is canonical"); + changes + .validate() + .expect("published chunk delta is canonical"); assert_eq!( changes.from_generation.as_ref(), Some(&parent.manifest().generation_id) From 2165c4c86de7a8b5d6ba09de0ffd99590f58f50e Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 19:39:10 -0800 Subject: [PATCH 09/14] test(code-index): prove sparse evidence survives filtering Preserve the concurrent fixture, formatting, and revision-22 golden repair. Require four actual unrelated call edges before taking read baselines and after restoring the measured successor, using qualified symbol identity. Add a falsifiable corruption regression: zero a nonempty target filter, recompute the outer manifest digest, and require the exact semantic evidence-descriptor refusal while the intact original still restores. Refs #4520 --- .../sparse_comment_publication.rs | 102 +++++++++++++++--- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs index 8b705ceed6e..c4fc19a01a8 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs @@ -8,18 +8,19 @@ use tracedecay_code_index::{ chunks::content_digest, incremental::plan_chunk_increment, production::{ - CodeIndexBuildRequestV1, CodeIndexCapturedFileV1, CodeIndexProductionOwnerV1, - CodeIndexPublishedGenerationV1, MemorySealedPublicationStoreV1, + CodeIndexBuildRequestV1, CodeIndexCapturedFileV1, CodeIndexProductionErrorV1, + CodeIndexProductionOwnerV1, CodeIndexPublishedGenerationV1, + MemorySealedPublicationStoreV1, }, }; use tracedecay_domain::{ - FileOccurrenceId, LanguageId, ManifestDigest, SanitizedCodeFileV1, SensitivityLevelV1, - SnapshotFileDispositionV1, SymbolOccurrenceId, + FileOccurrenceId, LanguageId, ManifestDigest, RelationEdgeKindV1, SanitizedCodeFileV1, + SensitivityLevelV1, SnapshotFileDispositionV1, SymbolOccurrenceId, }; use crate::{ production_orchestration::{ActiveControl, config, request_with_source}, - support::{cold_generation, id}, + support::{PartitionedSealV1, cold_generation, id, reseal_manifest}, }; fn tree_request(util: &str, sealed_at: i64, phase: &str) -> CodeIndexBuildRequestV1 { @@ -95,13 +96,33 @@ fn symbol(generation: &CodeIndexPublishedGenerationV1, name: &str) -> SymbolOccu .symbols() .symbols .iter() - .filter(|symbol| symbol.simple_name == name) + .filter(|symbol| symbol.qualified_name == name) .map(|symbol| symbol.occurrence.clone()) .collect::>(); assert_eq!(matches.len(), 1, "fixture must have exactly one {name}"); matches[0].clone() } +fn assert_unrelated_calls(generation: &CodeIndexPublishedGenerationV1) { + let target = symbol(generation, "src/other.rs::new"); + for n in 0..4 { + let caller = symbol(generation, &format!("src/caller{n}.rs::caller{n}")); + assert_eq!( + generation + .edges() + .iter() + .filter(|edge| { + edge.from_occurrence == caller + && edge.to_occurrence == target + && edge.kind == RelationEdgeKindV1::Calls + }) + .count(), + 1, + "caller{n} must bind the unrelated file, not just seal a descriptor" + ); + } +} + #[test] fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta() { let util = (0..8) @@ -117,6 +138,10 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta .build_and_publish(before_request.clone(), &ActiveControl) .expect("parent publishes"); assert!(parent.cold_reason().is_some()); + // Descriptor presence alone does not prove a resolved call. Establish + // the four actual unrelated edges before taking the read baselines. + let before = cold_generation(&parent); + assert_unrelated_calls(&before); let envelope: serde_json::Value = serde_json::from_slice(parent.manifest_bytes()).expect("sealed manifest envelope"); let evidence_digest = |path: &str| { @@ -132,7 +157,7 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta .expect("evidence descriptors") .iter() .find(|descriptor| descriptor["file_key"].as_u64() == Some(key)) - .expect("fixture must seal real cross-file evidence"); + .unwrap_or_else(|| panic!("{path} must seal real cross-file evidence")); serde_json::from_value::(descriptor["segment_digest"].clone()) .expect("evidence digest") }; @@ -179,9 +204,9 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta .build_and_publish(cold_request, &ActiveControl) .expect("cold successor publishes"); assert!(cold.cold_reason().is_some()); - let before = cold_generation(&parent); let after = cold_generation(&sparse); let expected = cold_generation(&cold); + assert_unrelated_calls(&after); let expected_delta = plan_chunk_increment(Some(before.chunks()), expected.chunks()) .expect("cold chunks define the complete expected delta"); assert!(!expected_delta.added_or_changed.is_empty()); @@ -205,16 +230,16 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta assert_eq!(changes.reused_digest, expected_delta.reused_digest); assert_eq!(after.edges(), expected.edges()); - let old_helper = symbol(&before, "helper"); - let helper = symbol(&after, "helper"); - let old_other = symbol(&before, "extra"); - let other = symbol(&after, "extra"); - let run = symbol(&after, "run"); - let sibling = symbol(&after, "second_caller"); + let old_helper = symbol(&before, "src/util.rs::helper"); + let helper = symbol(&after, "src/util.rs::helper"); + let old_other = symbol(&before, "src/util.rs::extra"); + let other = symbol(&after, "src/util.rs::extra"); + let run = symbol(&after, "src/main.rs::run"); + let sibling = symbol(&after, "src/sibling.rs::second_caller"); assert_ne!(old_helper, helper, "comment edit re-keys its target"); assert_ne!(old_other, other); - assert_eq!(run, symbol(&before, "run")); - assert_eq!(sibling, symbol(&before, "second_caller")); + assert_eq!(run, symbol(&before, "src/main.rs::run")); + assert_eq!(sibling, symbol(&before, "src/sibling.rs::second_caller")); for (from, to) in [(&run, &helper), (&run, &other), (&sibling, &helper)] { assert_eq!( after @@ -251,3 +276,48 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta "untouched caller fan-out survives repointing" ); } + +#[test] +fn sealed_evidence_refuses_a_resealed_false_negative_target_filter() { + let util = "pub fn helper() -> u32 { 1 }\npub fn extra() -> u32 { 2 }\n"; + let mut owner = + CodeIndexProductionOwnerV1::new(config(), MemorySealedPublicationStoreV1::default()) + .expect("owner"); + let published = owner + .build_and_publish(tree_request(util, 1_100_000, "filter"), &ActiveControl) + .expect("generation publishes"); + let generation = cold_generation(&published); + assert_unrelated_calls(&generation); + let seal = PartitionedSealV1::of(&generation); + let mut envelope = seal.envelope(); + let descriptor = envelope["generation"]["file_evidence"] + .as_array_mut() + .expect("evidence descriptors") + .iter_mut() + .find(|descriptor| { + descriptor["target_filter"] + .as_array() + .expect("target filter words") + .iter() + .any(|word| word.as_u64().expect("target filter word") != 0) + }) + .expect("real cross-file evidence must have a nonempty target filter"); + descriptor["target_filter"] = serde_json::json!([0_u64, 0_u64, 0_u64, 0_u64]); + // Recompute the outer digest so this reaches semantic descriptor checking, + // not an unrelated manifest authentication failure. Segment bytes stay intact. + let tampered = reseal_manifest(envelope); + CodeIndexPublishedGenerationV1::partitioned_text_metadata(&tampered) + .expect("resealed metadata is structurally valid"); + let error = seal + .restore(&tampered) + .expect_err("a false-negative target filter must not restore"); + assert!( + matches!( + &error, + CodeIndexProductionErrorV1::Contract(message) + if message == "sealed file evidence does not match its descriptor" + ), + "unexpected evidence refusal: {error}" + ); + assert_eq!(seal.restored().edges(), generation.edges()); +} From aadc478c960b229372c927e52d669cbb648d94e6 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sat, 7 Nov 2026 23:25:28 -0800 Subject: [PATCH 10/14] style(code-index): fix sparse publication import formatting Apply the exact Rust 2024 rustfmt change from the hosted Repository gates job for the sparse successor regression. Refs #4520 --- .../tests/code_index_suite/sparse_comment_publication.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs index c4fc19a01a8..10135fe4f84 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs @@ -9,8 +9,7 @@ use tracedecay_code_index::{ incremental::plan_chunk_increment, production::{ CodeIndexBuildRequestV1, CodeIndexCapturedFileV1, CodeIndexProductionErrorV1, - CodeIndexProductionOwnerV1, CodeIndexPublishedGenerationV1, - MemorySealedPublicationStoreV1, + CodeIndexProductionOwnerV1, CodeIndexPublishedGenerationV1, MemorySealedPublicationStoreV1, }, }; use tracedecay_domain::{ From b06807bebc1a8b8ac3395b7245f33566dc6f3783 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sun, 8 Nov 2026 12:46:33 -0800 Subject: [PATCH 11/14] test(code-index): verify sparse repointing against the cold corpus --- .../src/production/sparse_increment_tests.rs | 5 +-- .../sparse_comment_publication.rs | 34 +++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs b/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs index d1001bd79f5..602e4af2267 100644 --- a/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs +++ b/crates/tracedecay-code-index/src/production/sparse_increment_tests.rs @@ -343,7 +343,8 @@ fn a_comment_beside_same_named_symbols_decodes_only_what_it_reaches() { callers: &'a [(String, String)], ) -> Vec<(&'a str, &'a str, &'a str)> { let mut tree = rust_tree(util, MAIN); - tree[0].2 = "pub mod main;\npub mod util;\npub mod other;\n"; + tree[0].2 = "pub mod consumer;\npub mod util;\npub mod other;\n"; + tree[1].0 = "src/consumer.rs"; tree.push(("src/other.rs", RUST, other)); tree.extend( callers @@ -409,7 +410,7 @@ fn a_comment_beside_same_named_symbols_decodes_only_what_it_reaches() { .iter() .map(|(_, digest)| store.segment_reads(digest)) .collect::>(); - let dependent_evidence = evidence_of("src/main.rs"); + let dependent_evidence = evidence_of("src/consumer.rs"); let dependent_reads_before = store.segment_reads(&dependent_evidence); let sparse = publish( diff --git a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs index 10135fe4f84..d026fe77708 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs @@ -24,7 +24,7 @@ use crate::{ fn tree_request(util: &str, sealed_at: i64, phase: &str) -> CodeIndexBuildRequestV1 { let root = concat!( - "pub mod main;\npub mod util;\npub mod other;\npub mod sibling;\n", + "pub mod consumer;\npub mod util;\npub mod other;\npub mod sibling;\n", "pub mod caller0;\npub mod caller1;\npub mod caller2;\npub mod caller3;\n", ); let mut request = request_with_source( @@ -37,7 +37,7 @@ fn tree_request(util: &str, sealed_at: i64, phase: &str) -> CodeIndexBuildReques let mut sources = vec![ ("src/lib.rs".to_owned(), root.to_owned()), ( - "src/main.rs".to_owned(), + "src/consumer.rs".to_owned(), "pub fn run() -> u32 { crate::util::helper() + crate::util::extra() }\n".to_owned(), ), ( @@ -167,7 +167,7 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta (digest, reads) }) .collect::>(); - let dependent = evidence_digest("src/main.rs"); + let dependent = evidence_digest("src/consumer.rs"); let dependent_reads = store.segment_reads(&dependent); let edited = format!("{util}// comment only\n"); let cold_request = tree_request(&edited, 1_200_000, "after"); @@ -226,18 +226,40 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta assert_eq!(changes.added_or_changed, expected_delta.added_or_changed); assert_eq!(changes.deleted, expected_delta.deleted); assert_eq!(changes.reused_count, expected_delta.reused_count); - assert_eq!(changes.reused_digest, expected_delta.reused_digest); + let parent_replay = &parent + .manifest() + .source_commitments + .as_ref() + .expect("parent sealed its source") + .full_replay_digest; + let expected_reuse = tracedecay_domain::code_reused_partition_arc_share_digest( + parent_replay, + &parent.manifest().generation_id, + &sparse.manifest().generation_id, + expected_delta.reused_count, + ) + .expect("sparse reuse binds the parent to the cold delta's unchanged chunks"); + assert_eq!(changes.reused_digest, expected_reuse); + let corpus = |generation: &CodeIndexPublishedGenerationV1| { + generation + .chunks() + .chunks() + .iter() + .map(|chunk| (chunk.id.clone(), chunk.content_digest.clone())) + .collect::>() + }; + assert_eq!(corpus(&after), corpus(&expected)); assert_eq!(after.edges(), expected.edges()); let old_helper = symbol(&before, "src/util.rs::helper"); let helper = symbol(&after, "src/util.rs::helper"); let old_other = symbol(&before, "src/util.rs::extra"); let other = symbol(&after, "src/util.rs::extra"); - let run = symbol(&after, "src/main.rs::run"); + let run = symbol(&after, "src/consumer.rs::run"); let sibling = symbol(&after, "src/sibling.rs::second_caller"); assert_ne!(old_helper, helper, "comment edit re-keys its target"); assert_ne!(old_other, other); - assert_eq!(run, symbol(&before, "src/main.rs::run")); + assert_eq!(run, symbol(&before, "src/consumer.rs::run")); assert_eq!(sibling, symbol(&before, "src/sibling.rs::second_caller")); for (from, to) in [(&run, &helper), (&run, &other), (&sibling, &helper)] { assert_eq!( From 53fcf8e91d9f501a658231bf4163082479df66d5 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sun, 8 Nov 2026 01:52:34 -0800 Subject: [PATCH 12/14] test(automation): bind scheduler fixtures to backend identities Repair three hosted automation_runner_test failures by supplying the configured executable identity required by the production prefilter. Use the same identity for the prefilter and retained runner, preserving manual-schedule, exact-ledger, live-lock and lock-race assertions. Add a production-prefilter regression for unconfigured refusal, deduplication and same-occurrence recovery after binding, with zero backend calls and no effectful terminal. Do not relax backend admission, change timeouts, or alter the graph publication changes. Refs #4532 --- .../tests/automation_runner_test/jobs.rs | 124 ++++++++++++++++-- 1 file changed, 110 insertions(+), 14 deletions(-) diff --git a/crates/tracedecay/tests/automation_runner_test/jobs.rs b/crates/tracedecay/tests/automation_runner_test/jobs.rs index 740283d56e8..7342786381b 100644 --- a/crates/tracedecay/tests/automation_runner_test/jobs.rs +++ b/crates/tracedecay/tests/automation_runner_test/jobs.rs @@ -86,6 +86,7 @@ fn enabled_job_config() -> AutomationSettingsV1 { struct ContentBackend { calls: AtomicUsize, content: &'static str, + executable: Option, } impl ContentBackend { @@ -93,9 +94,15 @@ impl ContentBackend { Self { calls: AtomicUsize::new(0), content, + executable: None, } } + fn with_executable(mut self, executable: &Path) -> Self { + self.executable = Some(executable.to_path_buf()); + self + } + fn calls(&self) -> usize { self.calls.load(Ordering::SeqCst) } @@ -126,7 +133,7 @@ impl AgentTaskBackend for ContentBackend { } fn executable(&self) -> Option<&std::path::Path> { - None + self.executable.as_deref() } } @@ -988,6 +995,10 @@ async fn scheduler_trigger_skips_repeat_and_respects_lock_discipline() { let mut job = sample_job("sched-job"); job.schedule = Some("manual".to_string()); let config = enabled_job_config(); + // Reach the schedule gate with a bound identity, not the earlier + // backend-unconfigured refusal. This gate never invokes the backend. + let executable = tempfile::NamedTempFile::new().unwrap(); + let backend = ContentBackend::new("unused").with_executable(executable.path()); // Manual schedule: every scheduler tick of the same occurrence reaches // the prefilter, which records the skip once. @@ -996,7 +1007,7 @@ async fn scheduler_trigger_skips_repeat_and_respects_lock_discipline() { let run = evaluate_and_record_scheduler_skip( &dashboard_root, &config, - None, + backend.executable(), &job, "sched-occurrence", None, @@ -1019,7 +1030,7 @@ async fn scheduler_trigger_skips_repeat_and_respects_lock_discipline() { let repeated = evaluate_and_record_scheduler_skip( &dashboard_root, &config, - None, + backend.executable(), &job, "sched-occurrence", None, @@ -1031,6 +1042,76 @@ async fn scheduler_trigger_skips_repeat_and_respects_lock_discipline() { repeated.ledger_record, records[0], "an immediate repeat skip must return the exact durable prior row" ); + assert_eq!(backend.calls(), 0); +} + +#[tokio::test] +async fn scheduler_prefilter_rejects_unconfigured_backend_and_recovers_without_effect() { + let temp = tempdir().unwrap(); + let dashboard_root = temp.path().join("dashboard"); + let job = sample_job("backend-recovery"); + let config = enabled_job_config(); + let occurrence = "backend-recovery-occurrence"; + let backend = ContentBackend::new("unused"); + + let first = evaluate_and_record_scheduler_skip( + &dashboard_root, + &config, + backend.executable(), + &job, + occurrence, + None, + ) + .await + .unwrap() + .expect("a due job still requires a configured backend"); + assert_eq!(first.report["reason"], json!("backend_unconfigured")); + assert_eq!(first.ledger_record.status, AutomationRunStatus::Skipped); + assert_ne!(first.run_id, occurrence); + let repeated = evaluate_and_record_scheduler_skip( + &dashboard_root, + &config, + backend.executable(), + &job, + occurrence, + None, + ) + .await + .unwrap() + .unwrap(); + assert_eq!(repeated.ledger_record, first.ledger_record); + + let executable = tempfile::NamedTempFile::new().unwrap(); + let backend = backend.with_executable(executable.path()); + assert!( + evaluate_and_record_scheduler_skip( + &dashboard_root, + &config, + backend.executable(), + &job, + occurrence, + None, + ) + .await + .unwrap() + .is_none(), + "binding the backend must admit the same due occurrence without cooldown" + ); + assert_eq!(backend.calls(), 0); + assert_eq!( + load_run_records(&dashboard_root, 10).await.unwrap(), + vec![first.ledger_record], + "advisory admission must not append an effectful terminal" + ); + assert!( + tracedecay_automation_runtime::automation::run_ledger::find_run_record_exact_bounded( + &dashboard_root, + occurrence, + ) + .await + .unwrap() + .is_none() + ); } #[tokio::test] @@ -1206,15 +1287,22 @@ async fn scheduler_prefilter_live_lock_wins_over_not_due_summary() { let config = enabled_job_config(); let occurrence = "locked-prefilter-occurrence"; - let prefilter = - evaluate_and_record_scheduler_skip(&dashboard_root, &config, None, &job, occurrence, None) - .await - .unwrap() - .expect("the live job lock must produce a diagnostic"); + let executable = tempfile::NamedTempFile::new().unwrap(); + let backend = ContentBackend::new("unused").with_executable(executable.path()); + let prefilter = evaluate_and_record_scheduler_skip( + &dashboard_root, + &config, + backend.executable(), + &job, + occurrence, + None, + ) + .await + .unwrap() + .expect("the live job lock must produce a diagnostic"); assert_eq!(prefilter.report["reason"], json!("scheduler_lock_active")); assert_ne!(prefilter.run_id, occurrence); - let backend = ContentBackend::new("unused"); let admitted_job = tracedecay_automation_runtime::automation::jobs::admit_job(&job).unwrap(); let retained = run_user_job_with_backend_for_retained_settlement( &dashboard_root, @@ -1262,12 +1350,21 @@ async fn retained_scheduler_runner_reacquires_after_due_prefilter() { job.schedule = Some("every 1h".to_owned()); let config = enabled_job_config(); let occurrence = "prefilter-gap-occurrence"; + let executable = tempfile::NamedTempFile::new().unwrap(); + let backend = ContentBackend::new("unused").with_executable(executable.path()); assert!( - evaluate_and_record_scheduler_skip(&dashboard_root, &config, None, &job, occurrence, None) - .await - .unwrap() - .is_none(), + evaluate_and_record_scheduler_skip( + &dashboard_root, + &config, + backend.executable(), + &job, + occurrence, + None, + ) + .await + .unwrap() + .is_none(), "a new interval job is advisory-due before effect admission" ); let _raced_in = @@ -1276,7 +1373,6 @@ async fn retained_scheduler_runner_reacquires_after_due_prefilter() { .unwrap() .expect("the advisory prefilter must release its reservation lock"); - let backend = ContentBackend::new("unused"); let admitted_job = tracedecay_automation_runtime::automation::jobs::admit_job(&job).unwrap(); let retained = run_user_job_with_backend_for_retained_settlement( &dashboard_root, From 1e67b25e1d349cc9e2f938db3ea12738800905cc Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Sun, 8 Nov 2026 13:58:03 -0800 Subject: [PATCH 13/14] test(cli): observe daemon readiness before checking socket mode --- .../tests/core_cli_suite/tool_daemon_test.rs | 33 ++----------------- 1 file changed, 3 insertions(+), 30 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 0c65098e48c..429644ba3c3 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 @@ -543,21 +543,6 @@ fn configuration_tool_success( } } -fn wait_for_daemon_socket(socket_path: &Path) { - let deadline = Instant::now() + Duration::from_secs(10); - common::poll_until( - deadline, - Duration::from_millis(25), - || UnixStream::connect(socket_path).is_ok().then_some(()), - || { - format!( - "timed out waiting for daemon socket at {}", - socket_path.display() - ) - }, - ); -} - /// Enroll a project for the native hook capture contract: an enrollment /// marker binds the project root to a profile shard, and a daemon-issued /// hook configuration binding is published under that shard's data root so @@ -1718,21 +1703,9 @@ fn daemon_socket_is_owner_only() { let home = TempDir::new().unwrap(); let home_path = canonical_existing_path(home.path()); let socket_path = common::daemon_socket_path(&home_path); - let _ = std::fs::remove_file(&socket_path); - let mut daemon = common::DaemonProcess::new( - tracedecay_command_with_home(&home_path) - .arg("daemon") - .arg("run") - .arg("--socket") - .arg(&socket_path) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("tracedecay daemon should start"), - ); - - wait_for_daemon_socket(&socket_path); + let mut daemon = common::spawn_tracedecay_daemon_with(&home_path, |command| { + command.arg("--socket").arg(&socket_path); + }); let mode = std::fs::metadata(&socket_path) .expect("socket metadata") .permissions() From df2217b01e4a0148d3892bc681af84bab3295491 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Mon, 9 Nov 2026 15:40:03 -0800 Subject: [PATCH 14/14] test(code-index): measure sparse graph work before deduplication Count the existing graph-emission span around the actual cold and sparse publication calls. Require a positive whole-corpus cold control, exact three-owner sparse graph work, and byte-identical carried descriptors so redundant resealing cannot hide behind content-addressed deduplication. Check unchanged parent file-segment reads on that same successor alongside the existing evidence-read, cold-delta, graph and corruption assertions. No production, format, golden, dependency or workflow changes. Refs #4520 --- .../sparse_comment_publication.rs | 117 ++++++++++++++++-- 1 file changed, 104 insertions(+), 13 deletions(-) diff --git a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs index d026fe77708..266f5c98109 100644 --- a/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs +++ b/crates/tracedecay-code-index/tests/code_index_suite/sparse_comment_publication.rs @@ -2,6 +2,7 @@ //! including callers whose source did not change when a target was re-keyed. use std::sync::Arc; +use std::sync::atomic::{AtomicU64, Ordering}; use sha2::{Digest, Sha256}; use tracedecay_code_index::{ @@ -16,12 +17,44 @@ use tracedecay_domain::{ FileOccurrenceId, LanguageId, ManifestDigest, RelationEdgeKindV1, SanitizedCodeFileV1, SensitivityLevelV1, SnapshotFileDispositionV1, SymbolOccurrenceId, }; +use tracedecay_runtime_core::logging::set_tracing_capture; use crate::{ production_orchestration::{ActiveControl, config, request_with_source}, support::{PartitionedSealV1, cold_generation, id, reseal_manifest}, }; +// Count actual graph row emission, not publication writes: a redundant seal +// can regenerate the same digest and be deduplicated before reaching the store. +struct CountGraphEmissions(Arc); + +impl tracing::Subscriber for CountGraphEmissions { + fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool { + metadata.name() == "code_index.seal.collect.emit" + } + + fn new_span(&self, _span: &tracing::span::Attributes<'_>) -> tracing::span::Id { + tracing::span::Id::from_u64(self.0.fetch_add(1, Ordering::Relaxed) + 1) + } + + fn record(&self, _span: &tracing::span::Id, _values: &tracing::span::Record<'_>) {} + + fn record_follows_from(&self, _span: &tracing::span::Id, _follows: &tracing::span::Id) {} + + fn event(&self, _event: &tracing::Event<'_>) {} + + fn enter(&self, _span: &tracing::span::Id) {} + + fn exit(&self, _span: &tracing::span::Id) {} +} + +fn with_graph_emission_count(build: impl FnOnce() -> T) -> (T, u64) { + let emissions = Arc::new(AtomicU64::new(0)); + let _capture = set_tracing_capture(CountGraphEmissions(Arc::clone(&emissions))); + let result = build(); + (result, emissions.load(Ordering::Relaxed)) +} + fn tree_request(util: &str, sealed_at: i64, phase: &str) -> CodeIndexBuildRequestV1 { let root = concat!( "pub mod consumer;\npub mod util;\npub mod other;\npub mod sibling;\n", @@ -133,17 +166,23 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta let store = MemorySealedPublicationStoreV1::default(); let mut owner = CodeIndexProductionOwnerV1::new(config(), store.clone()).expect("owner"); let before_request = tree_request(&util, 1_100_000, "before"); - let parent = owner - .build_and_publish(before_request.clone(), &ActiveControl) - .expect("parent publishes"); + let (parent, cold_emissions) = with_graph_emission_count(|| { + owner.build_and_publish(before_request.clone(), &ActiveControl) + }); + let parent = parent.expect("parent publishes"); assert!(parent.cold_reason().is_some()); + assert_eq!( + cold_emissions, + before_request.snapshot.files.len() as u64, + "the cold control must observe actual graph work for every fixture file" + ); // Descriptor presence alone does not prove a resolved call. Establish // the four actual unrelated edges before taking the read baselines. let before = cold_generation(&parent); assert_unrelated_calls(&before); let envelope: serde_json::Value = serde_json::from_slice(parent.manifest_bytes()).expect("sealed manifest envelope"); - let evidence_digest = |path: &str| { + let segment_digest = |kind: &str, path: &str| { let key = before_request .snapshot .files @@ -151,23 +190,32 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta .position(|file| file.logical_path == path) .expect("fixture path"); let key = u64::try_from(key).expect("file key fits u64"); - let descriptor = envelope["generation"]["file_evidence"] + let descriptor = envelope["generation"][kind] .as_array() - .expect("evidence descriptors") + .expect("segment descriptors") .iter() .find(|descriptor| descriptor["file_key"].as_u64() == Some(key)) - .unwrap_or_else(|| panic!("{path} must seal real cross-file evidence")); + .unwrap_or_else(|| panic!("{path} must have a sealed {kind} descriptor")); serde_json::from_value::(descriptor["segment_digest"].clone()) - .expect("evidence digest") + .expect("segment digest") }; let unrelated = (0..4) - .map(|n| evidence_digest(&format!("src/caller{n}.rs"))) + .map(|n| segment_digest("file_evidence", &format!("src/caller{n}.rs"))) .map(|digest| { let reads = store.segment_reads(&digest); (digest, reads) }) .collect::>(); - let dependent = evidence_digest("src/consumer.rs"); + let unrelated_files = (0..4) + .map(|n| format!("src/caller{n}.rs")) + .chain(["src/other.rs".to_owned()]) + .map(|path| { + let digest = segment_digest("file_segments", &path); + let reads = store.segment_reads(&digest); + (path, digest, reads) + }) + .collect::>(); + let dependent = segment_digest("file_evidence", "src/consumer.rs"); let dependent_reads = store.segment_reads(&dependent); let edited = format!("{util}// comment only\n"); let cold_request = tree_request(&edited, 1_200_000, "after"); @@ -185,17 +233,60 @@ fn comment_successor_skips_unrelated_evidence_and_publishes_the_cold_chunk_delta .retain(|file| file.file_occurrence_id == edited_occurrence); assert_eq!(sparse_request.captured_files.len(), 1); sparse_request.changed_files = ["src/util.rs".to_owned()].into_iter().collect(); - let sparse = owner - .build_and_publish(sparse_request, &ActiveControl) - .expect("sparse successor publishes without recapturing callers"); + let (sparse, sparse_emissions) = + with_graph_emission_count(|| owner.build_and_publish(sparse_request, &ActiveControl)); + let sparse = sparse.expect("sparse successor publishes without recapturing callers"); assert_eq!(sparse.cold_reason(), None); // Observe reads before restoring either result, not on a second build. for (digest, reads) in unrelated { assert_eq!(store.segment_reads(&digest), reads, "unrelated evidence"); } + for (path, digest, reads) in unrelated_files { + assert_eq!(store.segment_reads(&digest), reads, "unrelated file {path}"); + } assert!(store.segment_reads(&dependent) > dependent_reads); + // Check the same measured publication before either result is restored. + // Equal descriptors alone would miss needless resealing of identical bytes. + let successor_envelope: serde_json::Value = + serde_json::from_slice(sparse.manifest_bytes()).expect("successor manifest"); + let parent_pages = envelope["generation"]["code_graph_pages"] + .as_array() + .expect("parent graph pages"); + let successor_pages = successor_envelope["generation"]["code_graph_pages"] + .as_array() + .expect("successor graph pages"); + assert_eq!(parent_pages.len(), before_request.snapshot.files.len()); + assert_eq!(successor_pages.len(), parent_pages.len()); + let mut changed_graph_paths = Vec::new(); + for parent_page in parent_pages { + let key = parent_page["file_key"].as_u64().expect("graph file key"); + let file_index = usize::try_from(key).expect("file key fits usize"); + let file = &before_request.snapshot.files[file_index]; + let path = file.logical_path.as_str(); + let successor_page = successor_pages + .iter() + .find(|page| page["file_key"].as_u64() == Some(key)) + .expect("every parent file retains a graph page"); + if successor_page["page_digest"] != parent_page["page_digest"] { + changed_graph_paths.push(path); + } else { + assert_eq!(successor_page, parent_page, "carried graph page {path}"); + } + } + changed_graph_paths.sort_unstable(); + assert_eq!( + changed_graph_paths, + vec!["src/consumer.rs", "src/sibling.rs", "src/util.rs"], + "only the edited file and its two real dependent owners change graph pages" + ); + assert_eq!( + sparse_emissions, + changed_graph_paths.len() as u64, + "unchanged graph pages must not be emitted again before digest deduplication" + ); + let mut cold_owner = CodeIndexProductionOwnerV1::new(config(), MemorySealedPublicationStoreV1::default()) .expect("cold owner");