Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c1d138f
perf(graph): avoid duplicate segment stat and time opens
ScriptedAlchemy Nov 8, 2026
737a350
style(graph): align new route regression formatting
ScriptedAlchemy Nov 8, 2026
214e3bf
fix(graph): validate retained route in seal verification
ScriptedAlchemy Nov 8, 2026
7d7ffc0
fix(graph): clear stale output after rejected segment reads
ScriptedAlchemy Nov 8, 2026
fe179ff
style(graph): satisfy Bazel rustfmt on segment regressions
ScriptedAlchemy Nov 8, 2026
9e0730f
fix(graph): clear output on interruption and partial read
ScriptedAlchemy Nov 8, 2026
c950f35
style(graph): normalize regression test spacing
ScriptedAlchemy Nov 8, 2026
baab1cb
style(graph): format sealed segment read diagnostic
ScriptedAlchemy Nov 8, 2026
ed860ca
fix(graph): preserve interruptions after segment I/O
ScriptedAlchemy Nov 8, 2026
36c47e5
fix(graph): clear rejected fingerprint page output
ScriptedAlchemy Nov 8, 2026
ffc7ef6
fix(graph): count only real sealed segment open attempts
ScriptedAlchemy Nov 8, 2026
73d04b3
style(graph): fix hosted Rust formatting gate
ScriptedAlchemy Nov 8, 2026
2fd183c
fix(graph): keep rejected cache reads from changing LRU order
ScriptedAlchemy Nov 8, 2026
2ecbdf1
test(automation): bind scheduler fixtures to backend identities
ScriptedAlchemy Nov 8, 2026
bd9b1a6
fix(graph): preserve late interruption during seal evidence reads
ScriptedAlchemy Nov 8, 2026
108b1ae
test(graph): permit the sealed evidence fault on read-only files
ScriptedAlchemy Nov 8, 2026
48b411c
fix(graph): import verification helper in proof regression
ScriptedAlchemy Nov 8, 2026
43db8db
style(graph): format sealed verification imports
ScriptedAlchemy Nov 8, 2026
6e1b285
fix(graph): preserve warm handles after failed first page read
ScriptedAlchemy Nov 8, 2026
f4f1bff
style(graph): format segment cache regression assertion
ScriptedAlchemy Nov 8, 2026
ef5fb64
fix(graph): use tail expressions for segment cache reads
ScriptedAlchemy Nov 8, 2026
d69cb9c
fix(graph): preserve late fallback interruptions
ScriptedAlchemy Nov 8, 2026
01deed6
test(graph): compare retained fallback bytes as slices
ScriptedAlchemy Nov 8, 2026
7c9e795
test(mcp): exercise routed queue ordering on the mounted store
ScriptedAlchemy Nov 8, 2026
063cc59
test(mcp): reuse the published graph for port order refusals
ScriptedAlchemy Nov 8, 2026
c2f21eb
fix(graph): preserve deadline after seal manifest proof I/O
ScriptedAlchemy Nov 9, 2026
35703d6
style(graph): format the seal proof interruption assertion
ScriptedAlchemy Nov 9, 2026
fb105c3
Merge master into fix/4532-graph-open-cost-telemetry
ScriptedAlchemy Nov 9, 2026
1e8f745
fix(graph): preserve late verified-seal read interruption
ScriptedAlchemy Nov 9, 2026
504aa7f
style(graph): keep late-interruption assertion rustfmt-safe
ScriptedAlchemy Nov 9, 2026
f4f3fbb
fix(graph): restore Bazel formatting and module imports
ScriptedAlchemy Nov 9, 2026
ce5a821
style(graph): satisfy Bazel rustfmt for late I/O regressions
ScriptedAlchemy Nov 9, 2026
05787ff
fix(graph): preserve late manifest interruption before digest refusal
ScriptedAlchemy Nov 9, 2026
8a19f09
Merge master into fix/4532-graph-open-cost-telemetry
ScriptedAlchemy Nov 9, 2026
f344a47
fix(graph): check interruption after sealed-manifest open steps
ScriptedAlchemy Nov 9, 2026
246732f
fix(graph): authenticate short-read seal prefixes
ScriptedAlchemy Nov 9, 2026
6bf169f
style(graph): format short-read proof regression
ScriptedAlchemy Nov 9, 2026
1d40f57
fix(graph): authenticate manifest proof from one read stream
ScriptedAlchemy Nov 10, 2026
30f4f99
style(graph): satisfy hosted manifest proof formatter
ScriptedAlchemy Nov 10, 2026
cbdf2f0
fix(graph): preserve deadlines after seal open filesystem probes
ScriptedAlchemy Nov 10, 2026
dc729fe
style(graph): normalize sealed-open regression spacing
ScriptedAlchemy Nov 10, 2026
f27944d
fix(graph): check budget after sealed row projection
ScriptedAlchemy Nov 10, 2026
295fede
fix(graph): preserve final seal validation deadlines
ScriptedAlchemy Nov 10, 2026
4608c9b
style(graph): apply hosted Bazel rustfmt corrections
ScriptedAlchemy Nov 10, 2026
afd72c2
chore(graph): integrate master without changing issue patch
ScriptedAlchemy Nov 10, 2026
32a10ea
perf(graph): buffer bounded attachment header reads
ScriptedAlchemy Nov 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 106 additions & 1 deletion crates/tracedecay-code-index/src/production/graph_page_store.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Independently readable code-graph output pages.

use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::io::{BufReader, Read, Seek, SeekFrom, Write};
use std::path::Path;

use serde::{Deserialize, Serialize};
Expand All @@ -15,6 +15,7 @@ use super::{

const CODE_GRAPH_PAGE_STORE_MAGIC_V1: &[u8; 8] = b"TDGRAPH1";
const CODE_GRAPH_PAGE_STORE_REVISION_V1: u32 = 1;
const CODE_GRAPH_HEADER_READ_BYTES: usize = 64 * 1024;

fn store_error(context: &str, error: impl std::fmt::Display) -> CodeIndexProductionErrorV1 {
CodeIndexProductionErrorV1::Contract(format!("code graph page store {context}: {error}"))
Expand Down Expand Up @@ -254,9 +255,18 @@ fn read_attachment_bytes(
Ok(encoded)
}

#[tracing::instrument(
name = "code_index.graph.page_store.decode_header",
level = "trace",
skip_all
)]
fn decode_attachment_header(
reader: impl Read,
) -> Result<CodeGraphPageStoreHeaderV1, CodeIndexProductionErrorV1> {
// serde_json does not buffer reader input. Keep the caller's header-only
// Take inside the buffer so refills neither issue one file read per byte
// nor consume page payloads that belong to subsequent reads.
let reader = BufReader::with_capacity(CODE_GRAPH_HEADER_READ_BYTES, reader);
serde_json::from_reader(reader).map_err(|error| store_error("header decode", error))
}

Expand Down Expand Up @@ -434,6 +444,21 @@ mod tests {
}
}

struct CountedFileReader {
file: File,
calls: usize,
bytes_read: u64,
}

impl Read for CountedFileReader {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
self.calls += 1;
let read = self.file.read(buffer)?;
self.bytes_read += read as u64;
Ok(read)
}
}

fn fixture_page() -> PersistedCodeGraphPageV1 {
let occurrence = FileOccurrenceId::new("file.fixture.0").expect("file occurrence");
PersistedCodeGraphPageV1 {
Expand Down Expand Up @@ -511,6 +536,86 @@ mod tests {
);
}

#[test]
fn real_attachment_header_batches_reads_and_preserves_page_boundaries() {
let scratch = tempfile::tempdir().expect("scratch");
let path = scratch.path().join("many-pages");
let generation =
CodeGenerationId::new("generation.v1.fixture.00000001.aaaaaaaa").expect("generation");
let pages = (0..256_u32)
.map(|index| {
let mut page = fixture_page();
page.file.file_occurrence_id =
FileOccurrenceId::new(format!("file.fixture.{index}")).unwrap();
page.file.logical_path = format!("src/module_{index:04}.rs");
page
})
.collect::<Vec<_>>();
let descriptors = pages
.iter()
.enumerate()
.map(|(index, page)| {
let mut descriptor = descriptor_with_decode_bytes(page, |size| size * 2);
descriptor.file_key = u32::try_from(index).unwrap();
descriptor
})
.collect::<Vec<_>>();
let mut writer =
CodeGraphPageStoreWriterV1::create(&path, &generation, PROJECTOR, &descriptors)
.expect("create real attachment");
for (descriptor, page) in descriptors.iter().zip(&pages) {
writer.write_page(descriptor, page).expect("write page");
}
writer.finish().expect("finish real attachment");

let mut file = File::open(&path).unwrap();
let mut prefix = [0_u8; 16];
file.read_exact(&mut prefix).unwrap();
let header_size = u64::from_le_bytes(prefix[8..].try_into().unwrap());
assert!(header_size > CODE_GRAPH_HEADER_READ_BYTES as u64);
let mut reader = CountedFileReader {
file,
calls: 0,
bytes_read: 0,
};
let header = decode_attachment_header((&mut reader).take(header_size)).unwrap();
assert_eq!(header.generation, generation);
assert_eq!(header.pages, descriptors);
assert_eq!(reader.bytes_read, header_size);
assert_eq!(reader.file.stream_position().unwrap(), 16 + header_size);
let read_bound = header_size.div_ceil(CODE_GRAPH_HEADER_READ_BYTES as u64) + 1;
assert!(reader.calls > 1, "the header must span real file reads");
assert!(
reader.calls as u64 <= read_bound,
"header decoding made {} file reads for {header_size} bytes",
reader.calls
);

// A positive read control observes the next page, not buffered data
// lost past the header boundary when its reader is dropped.
let header_reads = reader.calls;
let mut first_page_byte = [0_u8; 1];
reader.read_exact(&mut first_page_byte).unwrap();
assert_eq!(first_page_byte, [b'{']);
assert_eq!(reader.calls, header_reads + 1);
reader.file.seek(SeekFrom::Start(16)).unwrap();
assert!(decode_attachment_header((&mut reader).take(header_size - 1)).is_err());
assert_eq!(reader.file.stream_position().unwrap(), 16 + header_size - 1);
drop(reader);

let mut store = FileCodeGraphPageStoreV1::open(&path, PROJECTOR)
.expect("open the production attachment")
.expect("current revision");
assert_eq!(store.pages(), descriptors.as_slice());
for (descriptor, page) in descriptors.iter().zip(&pages) {
let recovered = store.read_page(descriptor).expect("verify every page");
assert_eq!(
serde_json::to_vec(&recovered).unwrap(),
serde_json::to_vec(page).unwrap()
);
}
}

#[test]
fn opening_refuses_a_page_larger_than_its_admitted_decode_bytes() {
let scratch = tempfile::tempdir().expect("scratch");
Expand Down
Loading
Loading