Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
e5feef2
perf(code-index): pin staged segment reads per preparation
ScriptedAlchemy Nov 8, 2026
cbe966b
fix(code-index): scope staged reader test imports to Unix
ScriptedAlchemy Nov 8, 2026
0d6c177
perf(code-index): expose staged open counts in tracing
ScriptedAlchemy Nov 8, 2026
b1cfe79
fix(code-index): refuse replaced pinned staging files
ScriptedAlchemy Nov 8, 2026
ce6566c
fix(code-index): poison replaced staging reads and count open attempts
ScriptedAlchemy Nov 8, 2026
4dfd243
fix(code-index): refuse publication after staged identity failure
ScriptedAlchemy Nov 8, 2026
38137c8
fix(code-index): recheck path identity without another open
ScriptedAlchemy Nov 8, 2026
21827ea
style(code-index): format staged segment reader regressions
ScriptedAlchemy Nov 8, 2026
262e340
fix(code-index): preserve staged read identity across mutations
ScriptedAlchemy Nov 8, 2026
b850dcb
fix(code-index): retain sealed identities after handle eviction
ScriptedAlchemy Nov 8, 2026
18269c6
test(code-index): verify staged reads through durable reload
ScriptedAlchemy Nov 8, 2026
103101b
style(code-index): format pinned segment read regression
ScriptedAlchemy Nov 8, 2026
c76238b
fix(code-index): resolve Bazel Clippy failures in staged reads
ScriptedAlchemy Nov 8, 2026
3286852
perf(storage): flush staged files through bounded writer handles
ScriptedAlchemy Nov 8, 2026
4d0ad18
fix(code-index): preserve cached handles on rejected reads
ScriptedAlchemy Nov 8, 2026
688f3a6
style(storage): satisfy Bazel formatting gate
ScriptedAlchemy Nov 8, 2026
70188c5
fix(code-index): never replace raced sealed segment names
ScriptedAlchemy Nov 8, 2026
7184d12
perf(code-index): avoid directory opens during publication checks
ScriptedAlchemy Nov 8, 2026
df3986a
fix(code-index): preserve immutable manifest publication and reuse fd
ScriptedAlchemy Nov 8, 2026
b3bc9ec
fix(code-index): use atomic immutable publish for manifest names
ScriptedAlchemy Nov 8, 2026
bcb039e
style(code-index): align staged segment reads with rustfmt
ScriptedAlchemy Nov 8, 2026
1d021a5
fix(code-index): refuse retained reads after I/O interruption
ScriptedAlchemy Nov 8, 2026
ae10ea5
style(code-index): format retained read interruption regression
ScriptedAlchemy Nov 8, 2026
0f9e7f2
fix(code-index): authenticate cold whole-segment reads
ScriptedAlchemy Nov 8, 2026
5a9edde
test(code-index): use valid fixture and canonical regression layout
ScriptedAlchemy Nov 8, 2026
1b53e6d
perf(storage): reject invalid batch members before window fsync
ScriptedAlchemy Nov 8, 2026
5f9c013
perf(code-index): reuse authenticated segment reads at commit
ScriptedAlchemy Nov 8, 2026
0d749b0
fix(code-index): preserve pending segment identity on dedup
ScriptedAlchemy Nov 8, 2026
108d864
style(code-index): apply pinned Rust format to staged regression
ScriptedAlchemy Nov 8, 2026
8a2ffc0
perf(code-index): reuse canonical segment witness on repeated staging
ScriptedAlchemy Nov 8, 2026
045bb99
fix(code-index): reject unflushed segment after staging failure
ScriptedAlchemy Nov 8, 2026
97814f3
style(code-index): format failed-admission regression
ScriptedAlchemy Nov 8, 2026
9b34ee7
style(code-index): format staged segment regressions
ScriptedAlchemy Nov 8, 2026
a65bea9
perf(code-index): authenticate staged bytes without commit opens
ScriptedAlchemy Nov 8, 2026
2200bd0
style(code-index): match hosted Bazel Rust formatting
ScriptedAlchemy Nov 8, 2026
9c08e11
fix(code-index): bind batch admission to authenticated writer identity
ScriptedAlchemy Nov 8, 2026
badb863
perf(code-index): validate active pointer through one open
ScriptedAlchemy Nov 8, 2026
e7ab03f
style(code-index): normalize pointer open regression formatting
ScriptedAlchemy Nov 8, 2026
da385bc
perf(code-index): reuse published pointer writer for memo validation
ScriptedAlchemy Nov 8, 2026
ed1786b
style(code-index): align staged publication with rustfmt
ScriptedAlchemy Nov 8, 2026
c851742
fix(code-index): retain digest for generation collision regression
ScriptedAlchemy Nov 8, 2026
1bcaf00
fix(code-index): authenticate evidence packs through writer handles
ScriptedAlchemy Nov 8, 2026
402923f
test(code-index): witness evidence pack writer identity
ScriptedAlchemy Nov 8, 2026
8dda2ad
fix(code-index): validate evidence pack file kind before digest
ScriptedAlchemy Nov 8, 2026
96daef5
test(code-index): exercise publication retries under a shared lease
ScriptedAlchemy Nov 8, 2026
650e1cc
fix(code-index): reject substituted staged packs before naming
ScriptedAlchemy Nov 8, 2026
8a683a3
test(automation): bind scheduler fixtures to backend identities
ScriptedAlchemy Nov 8, 2026
9e80313
perf(code-index): reuse parsed pointer bytes for publication CAS
ScriptedAlchemy Nov 8, 2026
08a2177
style(code-index): format publication and staged read changes
ScriptedAlchemy Nov 8, 2026
8ea3d31
fix(code-index): bind memoized pointer to named inode
ScriptedAlchemy Nov 8, 2026
c42535c
fix(code-index): scope Linux watcher trait and own test identity
ScriptedAlchemy Nov 8, 2026
46c5d97
style(code-index): satisfy Rustfmt for native pointer regressions
ScriptedAlchemy Nov 8, 2026
5f2e890
fix(code-index): authenticate cold pinned whole reads
ScriptedAlchemy Nov 9, 2026
95a93a8
style(code-index): satisfy staged corruption test rustfmt
ScriptedAlchemy Nov 9, 2026
262cf1f
test(code-index): compare the canonical pointer generation ID
ScriptedAlchemy Nov 9, 2026
5ffbb82
test(code-index): compare the captured pointer bytes explicitly
ScriptedAlchemy Nov 9, 2026
b42f7ca
chore(ci): stack staged segment reads on launcher distribution
ScriptedAlchemy Nov 9, 2026
4ea334e
Merge master into fix/4533-staged-segment-read-opens
ScriptedAlchemy Nov 9, 2026
e9b6e8e
fix(storage): recheck flushed windows after final barriers
ScriptedAlchemy Nov 9, 2026
835bd99
Merge current master into fix/4533-staged-segment-read-opens
ScriptedAlchemy Nov 9, 2026
76b7556
fix(code-index): poison staged reads after digest failures
ScriptedAlchemy Nov 9, 2026
d3b089d
Merge master into fix/4533-staged-segment-read-opens
ScriptedAlchemy Nov 9, 2026
ec0c903
style(storage): apply hosted Bazel rustfmt to durability regression
ScriptedAlchemy Nov 9, 2026
2e1b241
fix(code-index): preserve cached handles on failed cold opens
ScriptedAlchemy Nov 9, 2026
e18d295
fix(code-index): reject deduplication across swapped inodes
ScriptedAlchemy Nov 9, 2026
8e17075
fix(code-index): retain staged inode authority through deduplication
ScriptedAlchemy Nov 9, 2026
52b335d
fix(code-index): honor tracing capture policy in storage regressions
ScriptedAlchemy Nov 9, 2026
3d12658
style(code-index): format durability retry assertions
ScriptedAlchemy Nov 9, 2026
33d1b0c
style(code-index): apply hosted Bazel rustfmt corrections
ScriptedAlchemy Nov 9, 2026
0384da6
test(private-fs): exercise final fsync identity at owning target
ScriptedAlchemy Nov 9, 2026
6c0b52e
fix(storage): keep fsync pause crate-test-only
ScriptedAlchemy Nov 9, 2026
ff7015e
style(private-fs): apply hosted Bazel rustfmt corrections
ScriptedAlchemy Nov 9, 2026
dacc5fc
perf(code-index): reuse retained segment descriptors
ScriptedAlchemy Nov 9, 2026
b91f221
fix(code-index): format retained-read regression for CI
ScriptedAlchemy Nov 9, 2026
3bbff2b
perf(retention): reuse manifest descriptor across mark probe
ScriptedAlchemy Nov 9, 2026
b46aeeb
fix(code-index): preserve healthy handles on failed cold reads
ScriptedAlchemy Nov 9, 2026
8d260d5
fix(retention): preserve cancellation at manifest open boundary
ScriptedAlchemy Nov 9, 2026
598c1ff
perf(code-index): reuse retained segment handles across requests
ScriptedAlchemy Nov 9, 2026
9ea8a39
fix(code-index): apply hosted Bazel formatting corrections
ScriptedAlchemy Nov 9, 2026
61394f7
fix(code-index): retire cached descriptors before generation GC
ScriptedAlchemy Nov 9, 2026
43ca411
fix(code-index): format retention cache release for Bazel
ScriptedAlchemy Nov 9, 2026
fba00d6
perf(code-index): reuse authenticated publication pointer descriptor
ScriptedAlchemy Nov 9, 2026
0accee8
fix(code-index): close pointer handles before scope reset
ScriptedAlchemy Nov 9, 2026
63a01e7
fix(code-index): repair Bazel retention and reset regressions
ScriptedAlchemy Nov 10, 2026
ec15c30
fix(code-index): align cached handle type visibility
ScriptedAlchemy Nov 10, 2026
46c0141
fix(code-index): keep native Bazel retention tests compiling
ScriptedAlchemy Nov 10, 2026
a840b5c
perf(code-index): reuse historical generation lock descriptor
ScriptedAlchemy Nov 10, 2026
30747aa
test(code-index): prove cached reader lock exclusion and inode refusal
ScriptedAlchemy Nov 10, 2026
53b765c
fix(code-index): reject symlink aliases of cached read lock
ScriptedAlchemy Nov 10, 2026
20c5a35
fix(code-index): refuse symlinked pointer publication slots
ScriptedAlchemy Nov 10, 2026
ebb9f1d
fix(code-index): refuse changed cold pointer authorities
ScriptedAlchemy Nov 10, 2026
fcbe281
test(code-index): reauthenticate pointer ctime after rename
ScriptedAlchemy Nov 10, 2026
e86a4e5
fix(code-index): distinguish pointer replacement from corruption
ScriptedAlchemy Nov 10, 2026
33df990
style(code-index): apply hosted Bazel rustfmt corrections
ScriptedAlchemy Nov 10, 2026
b852ca0
fix(private-fs): revalidate completed durability windows
ScriptedAlchemy Nov 10, 2026
108b39f
style(private-fs): apply hosted Bazel formatting corrections
ScriptedAlchemy Nov 10, 2026
60f5bcd
fix(code-index): authenticate retention generation metadata 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

use std::collections::{BTreeMap, BTreeSet};
use std::fs::File;
use std::io::{BufReader, Read};
use std::io::{BufReader, Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, Instant};
Expand Down Expand Up @@ -55,6 +55,8 @@ pub use graph_replay_release::{
CodeGenerationGraphReplayReleasePageV1, CodeGenerationGraphReplayReleaseV1,
code_generation_graph_replay_release_page, complete_code_generation_graph_replay_release,
};
#[cfg(unix)]
pub use locking::CachedCodeGenerationStoreReadLockV1;
use locking::acquire_generation_segments_lock_checked;
pub use locking::{
CodeGenerationStoreLockV1, acquire_code_generation_store_lock,
Expand Down Expand Up @@ -112,7 +114,7 @@ use text_artifacts::{
plan_collectable_text_artifacts_cancellable, stage_collectable_text_artifacts,
};

use generation_scan::{read_generation_format_revision, read_generation_metadata};
use generation_scan::{read_generation_format_revision_from_file, read_generation_metadata};
#[cfg(test)]
use scope_quarantine::ScopeQuarantineAuthority;

Expand Down Expand Up @@ -435,6 +437,13 @@ pub enum CodeGenerationRetentionErrorV1 {
TargetHeld(String),
}

/// A daemon-owned generation reader releases its open segment descriptors
/// before the retention executor unlinks any corresponding store entries.
/// The callback runs under the exclusive generation and shared-segment locks;
/// a refusal preserves the existing files and their durable receipts.
pub type RetainedSegmentHandleRetirementV1 =
Arc<dyn Fn() -> Result<(), CodeGenerationRetentionErrorV1> + Send + Sync>;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CodeGenerationRetentionModeV1 {
DryRun,
Expand Down Expand Up @@ -1414,13 +1423,30 @@ fn sweep_unreferenced_generation_segments(
})?
.to_owned()
};
if read_generation_format_revision(&path, is_cancelled)?
// The revision probe and full descriptor mark must observe the
// same opened inode. Reopening here paid a second endpoint-security
// authorization for every current-revision manifest and could read
// different bytes if the pathname changed between the two opens.
// Cancellation must win immediately before endpoint-authorized
// open(), including when a listed generation disappeared meanwhile.
if is_cancelled() {
return Err(CodeGenerationRetentionErrorV1::Cancelled);
}
let mut file = File::open(&path).map_err(|error| {
if is_cancelled() {
CodeGenerationRetentionErrorV1::Cancelled
} else {
deferred_if_absent(error)
}
})?;
if read_generation_format_revision_from_file(&mut file, &path, is_cancelled)?
!= SEALED_GENERATION_FORMAT_REVISION_V1
{
continue;
}
file.seek(SeekFrom::Start(0)).map_err(storage)?;
let mut reader = CancellableGenerationManifestReaderV1 {
file: File::open(&path).map_err(deferred_if_absent)?,
file,
hasher: Sha256::new(),
is_cancelled,
cancelled: false,
Expand Down Expand Up @@ -1757,6 +1783,7 @@ pub fn execute_code_generation_retention(
completed_at,
graph_replay_pool_root,
&|| false,
None,
)
}

Expand All @@ -1776,6 +1803,7 @@ pub fn execute_code_generation_retention_cancellable(
completed_at: UtcMicros,
graph_replay_pool_root: Option<&Path>,
is_cancelled: &dyn Fn() -> bool,
retire_cached_segments: Option<&RetainedSegmentHandleRetirementV1>,
) -> Result<CodeGenerationRetentionReportV1, CodeGenerationRetentionErrorV1> {
if is_cancelled() {
return Err(CodeGenerationRetentionErrorV1::Cancelled);
Expand Down Expand Up @@ -1819,6 +1847,16 @@ pub fn execute_code_generation_retention_cancellable(
"active generation changed after the retention mark phase".to_owned(),
));
}
// Readers take the shared store lock before consulting their descriptor
// cache. Once this exclusive lock is held there can be no in-flight
// retained read; empty the daemon's cache before unlinking any segment.
// Otherwise POSIX keeps deleted segment bytes allocated indefinitely.
if (!plan.collectable_generations.is_empty()
|| plan.collectable_generation_segments == GenerationSegmentCensusV1::Present)
&& let Some(retire) = retire_cached_segments
{
retire()?;
}
let mut generation_segment_batch_exhausted = if plan.collectable_generations.is_empty()
&& plan.collectable_generation_segments == GenerationSegmentCensusV1::Present
{
Expand Down Expand Up @@ -2095,6 +2133,7 @@ fn run_code_generation_retention_cancellable(
completed_at,
graph_replay_pool_root,
is_cancelled,
None,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::fs::File;
use std::io::Read;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use std::path::Path;

use sha2::{Digest, Sha256};
Expand All @@ -14,14 +16,14 @@ use super::{
const MAX_FORMAT_REVISION_PREFIX_BYTES: usize = 4 * 1024;
const GENERATION_METADATA_READ_CHUNK_BYTES: usize = 4 * 1024;

pub(super) fn read_generation_format_revision(
pub(super) fn read_generation_format_revision_from_file(
file: &mut File,
path: &Path,
is_cancelled: &dyn Fn() -> bool,
) -> Result<u32, CodeGenerationRetentionErrorV1> {
if is_cancelled() {
return Err(CodeGenerationRetentionErrorV1::Cancelled);
}
let file = File::open(path).map_err(deferred_if_absent)?;
let mut prefix = Vec::with_capacity(MAX_FORMAT_REVISION_PREFIX_BYTES);
file.take(MAX_FORMAT_REVISION_PREFIX_BYTES as u64)
.read_to_end(&mut prefix)
Expand All @@ -47,8 +49,25 @@ pub(super) fn read_generation_metadata(
if is_cancelled() {
return Err(CodeGenerationRetentionErrorV1::Cancelled);
}
// Enumerating a content-addressed name does not authorize following an
// alias to another generation. This metadata witness adds no pathname open.
let named_before = path.symlink_metadata().map_err(deferred_if_absent)?;
if !named_before.file_type().is_file() {
return Err(CodeGenerationRetentionErrorV1::UnsafeState(format!(
"generation file '{}' is not a regular file",
path.display()
)));
}
let mut file = File::open(path).map_err(deferred_if_absent)?;
let size_bytes = file.metadata().map_err(storage)?.len();
let opened_before = file.metadata().map_err(storage)?;
#[cfg(unix)]
if !same_generation_file_identity(&named_before, &opened_before) {
return Err(CodeGenerationRetentionErrorV1::UnsafeState(format!(
"generation file '{}' changed while opening",
path.display()
)));
}
let size_bytes = opened_before.len();
let mut hasher = Sha256::new();
let (format_revision, manifest) =
read_generation_metadata_prefix(&mut file, path, verification, &mut hasher, is_cancelled)?;
Expand All @@ -75,9 +94,44 @@ pub(super) fn read_generation_metadata(
}
GenerationDigestVerificationV1::MetadataOnly => named_state_digest(path)?,
};
// The manifest, digest and reported size must belong to the pathname
// enumerated by retention, not a replaced inode or modified old handle.
// Metadata-only scans cannot fall back to trusting a substituted name.
let named_after = path.symlink_metadata().map_err(deferred_if_absent)?;
let opened_after = file.metadata().map_err(storage)?;
#[cfg(unix)]
let unchanged = same_generation_file_identity(&named_before, &named_after)
&& same_generation_file_identity(&named_before, &opened_after);
#[cfg(not(unix))]
let unchanged = named_after.file_type().is_file()
&& opened_after.is_file()
&& named_after.len() == size_bytes
&& opened_after.len() == size_bytes;
if !unchanged {
return Err(CodeGenerationRetentionErrorV1::UnsafeState(format!(
"generation file '{}' changed during metadata read",
path.display()
)));
}
Ok((format_revision, manifest, state_digest, size_bytes))
}

#[cfg(unix)]
fn same_generation_file_identity(
original: &std::fs::Metadata,
current: &std::fs::Metadata,
) -> bool {
original.file_type().is_file()
&& current.file_type().is_file()
&& original.dev() == current.dev()
&& original.ino() == current.ino()
&& original.len() == current.len()
&& original.mtime() == current.mtime()
&& original.mtime_nsec() == current.mtime_nsec()
&& original.ctime() == current.ctime()
&& original.ctime_nsec() == current.ctime_nsec()
}

/// Read only as far as complete metadata requires, up to the existing bound.
/// Geometrically spaced probes keep repeated prefix parsing linear in bytes
/// even when a manifest is large. Reads stay small and cancellable between
Expand Down Expand Up @@ -233,6 +287,8 @@ mod tests {
use std::cell::Cell;
use std::fs::{self, File, OpenOptions};
use std::io::{Seek, SeekFrom, Write};
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};

use sha2::{Digest, Sha256};
Expand All @@ -242,7 +298,8 @@ mod tests {
use super::{
CodeGenerationRetentionErrorV1, GENERATION_METADATA_READ_CHUNK_BYTES,
GenerationDigestVerificationV1, MAX_GENERATION_METADATA_PREFIX_BYTES, parse_json_u32_field,
read_generation_format_revision, read_generation_metadata, read_generation_metadata_prefix,
read_generation_format_revision_from_file, read_generation_metadata,
read_generation_metadata_prefix,
};

fn generation_bytes(manifest_padding: usize, payload_bytes: usize) -> Vec<u8> {
Expand Down Expand Up @@ -270,10 +327,13 @@ mod tests {
let root = tempfile::tempdir().unwrap();
let bytes = generation_bytes(0, 256 * 1024);
let (path, expected_digest) = write_generation(root.path(), &bytes);
let mut revision_file = File::open(&path).unwrap();
assert_eq!(
read_generation_format_revision(&path, &|| false).unwrap(),
read_generation_format_revision_from_file(&mut revision_file, &path, &|| false)
.unwrap(),
SEALED_GENERATION_FORMAT_REVISION_V1
);
drop(revision_file);
let mut file = File::open(&path).unwrap();
let (revision, manifest) = read_generation_metadata_prefix(
&mut file,
Expand Down Expand Up @@ -428,6 +488,71 @@ mod tests {
);
}

#[cfg(unix)]
#[test]
fn generation_metadata_refuses_symlink_aliases_in_both_modes() {
let root = tempfile::tempdir().unwrap();
let bytes = generation_bytes(0, 32 * 1024);
let (target, _) = write_generation(root.path(), &bytes);
let alias = root.path().join(format!("generation-{}.json", "a".repeat(64)));
std::os::unix::fs::symlink(&target, &alias).unwrap();
assert_eq!(fs::read(&alias).unwrap(), bytes, "alias resolves to valid bytes");

for mode in [
GenerationDigestVerificationV1::MetadataOnly,
GenerationDigestVerificationV1::Full,
] {
assert!(matches!(
read_generation_metadata(&alias, mode, &|| false),
Err(CodeGenerationRetentionErrorV1::UnsafeState(_))
));
}
let (_, _, digest, size) =
read_generation_metadata(&target, GenerationDigestVerificationV1::Full, &|| false)
.unwrap();
assert_eq!(digest, encode_tagged_lowercase_hex("sha256:", &Sha256::digest(&bytes)));
assert_eq!(size, bytes.len() as u64);
}

#[cfg(unix)]
#[test]
fn generation_metadata_refuses_same_byte_inode_rotation_during_read() {
for mode in [
GenerationDigestVerificationV1::MetadataOnly,
GenerationDigestVerificationV1::Full,
] {
let root = tempfile::tempdir().unwrap();
let bytes = generation_bytes(0, 64 * 1024);
let (path, _) = write_generation(root.path(), &bytes);
let substitute = root.path().join("substitute.tmp");
fs::write(&substitute, &bytes).unwrap();
let original = fs::metadata(&path).unwrap();
let foreign = fs::metadata(&substitute).unwrap();
assert_ne!(
(original.dev(), original.ino()),
(foreign.dev(), foreign.ino()),
"the replacement must use a different real inode"
);
let probes = Cell::new(0_usize);
let result = read_generation_metadata(&path, mode, &|| {
let previous = probes.get();
probes.set(previous + 1);
// The third cancellation checkpoint is after reading the
// first nonempty prefix from the already-open descriptor.
if previous == 2 {
fs::rename(&substitute, &path).unwrap();
}
false
});
assert!(probes.get() >= 3, "the real reader must cross the swap");
assert!(matches!(
result,
Err(CodeGenerationRetentionErrorV1::UnsafeState(_))
));
assert_eq!(fs::read(&path).unwrap(), bytes);
}
}

#[test]
fn format_revision_requires_a_complete_unsigned_json_integer() {
for input in [
Expand Down
Loading
Loading