Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
55f2724
mason: compose view generations and replay installation cuts
ualtinok Oct 25, 2026
7aab4ea
mason: retain composed semantic readers through bundle retirement
ualtinok Oct 25, 2026
d8d3f59
mason: preserve snapshot caches and summary ownership after rebase
ualtinok Oct 26, 2026
8c5859b
mason: fix Athena F2/c1 reader I/O under shared locks
ualtinok Oct 26, 2026
29bde9c
mason: strengthen Athena T1/T2 publication boundary tests
ualtinok Oct 26, 2026
4e47fb5
mason: fix Athena F1/c5 cleanup after a possible landed CAS
ualtinok Oct 26, 2026
117e0a5
mason: fix Athena c7 sparse fold ownership and fill content
ualtinok Oct 26, 2026
a77f6aa
mason: fix Athena F5/c4 watcher and membership install fences
ualtinok Oct 26, 2026
de71b83
mason: fix Athena c10/c2/c3 post-publication lifecycle
ualtinok Oct 26, 2026
b0efd2e
mason: fence Athena c6 unauthenticated bases until s3
ualtinok Oct 26, 2026
cbc8125
mason: fix Athena superseded cuts and late publication bases
ualtinok Oct 26, 2026
6117187
mason: fix Athena plane applicability coverage
ualtinok Oct 26, 2026
45a042e
mason: reject Athena f4 nonmember composition tombstones
ualtinok Oct 26, 2026
7b7a814
mason: fix Athena F11 replay and callgraph key coherence
ualtinok Oct 26, 2026
446104e
mason: fix Athena C3/C6/C8 held semantic fill lifetime
ualtinok Oct 26, 2026
160b0e2
mason: complete Athena c10 per-view probe maintenance exclusion
ualtinok Oct 26, 2026
b4c3e00
mason: assert Athena early-fill race fixture is reached
ualtinok Oct 26, 2026
0b6c95a
mason: recognize the harness prototype's completed strict walk
ualtinok Oct 26, 2026
50cb08b
release notes and search-quality descriptor for views-4-2-composition…
ualtinok Oct 26, 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
@@ -0,0 +1,8 @@
{
"targeted_mechanism": "none",
"kind": "harness",
"slice_class": "non_ranking",
"fixtures": [
"harness-goldens"
]
}
7 changes: 3 additions & 4 deletions crates/aft/src/executor/view_publication_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2088,7 +2088,7 @@ fn callgraph_producer_change_republishes_an_existing_generation() {
let (_, built) = load();
let views = owner.view_store().unwrap();
let built_name = built.snapshot.generation().name().to_string();
let mut previous = built.snapshot.generation().manifest().clone();
let mut previous = built.snapshot.generation().flatten().unwrap();
previous.header_mut().producers.callgraph = PREVIOUS_PRODUCER.into();
let previous_name = GenerationName::for_manifest(&previous).unwrap();
let store = owner.open_store(FamilyPlane::Callgraph).unwrap();
Expand Down Expand Up @@ -2117,7 +2117,7 @@ fn callgraph_producer_change_republishes_an_existing_generation() {
);
let generation = current.snapshot.generation();
assert_ne!(generation.name(), previous_name);
assert_eq!(generation.manifest().header().producers.callgraph, PRODUCER);
assert_eq!(generation.header().producers.callgraph, PRODUCER);
assert_eq!(
views.current_generation().unwrap().as_deref(),
Some(generation.name())
Expand All @@ -2134,8 +2134,7 @@ fn callgraph_producer_change_republishes_an_existing_generation() {
// The served database equals a cold build of the new manifest.
let cold_dir = tempfile::tempdir().unwrap();
let cold = cold_dir.path().join("cold.sqlite");
crate::views::callgraph::materialize(&cold, generation.manifest(), &BlobReader(store.reader()))
.unwrap();
crate::views::callgraph::materialize(&cold, generation, &BlobReader(store.reader())).unwrap();
crate::views::materialization::parity::assert_logical_parity(
&views.derived_path(generation.name()).unwrap(),
&cold,
Expand Down
31 changes: 31 additions & 0 deletions crates/aft/src/pins/live.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ impl LivePin {
Ok(())
}

/// A completed fill may leave its live pin only after durable publication
/// protects it. Unpublished override keys survive even if content equality
/// removed their corresponding live entry.
pub fn trim_published(
&mut self,
published: &BTreeSet<String>,
keep: impl Fn(&str) -> bool,
) -> Result<(), PinError> {
self.trim(|key| keep(key) || !published.contains(key))
}

pub fn len(&self) -> usize {
self.keys.len()
}
Expand Down Expand Up @@ -214,6 +225,26 @@ mod tests {
.collect()
}

#[test]
fn override_fill_stays_live_until_durable_publication_protects_it() {
let view = tempfile::tempdir().unwrap();
let mut pin = LivePin::create_in(view.path(), "family".into(), "worker".into()).unwrap();
let key = keys(1)[0];
pin.protect(&[key]).unwrap();
pin.trim_published(&BTreeSet::new(), |_| false).unwrap();
assert_eq!(
pin.len(),
1,
"content equality cannot retire an unpublished override key"
);
assert!(fs::read_to_string(pin.keys_path())
.unwrap()
.contains(&key.to_hex()));
pin.trim_published(&BTreeSet::from([key.to_hex()]), |_| false)
.unwrap();
assert!(pin.is_empty());
}

#[test]
fn protecting_a_batch_writes_the_key_file_once_without_sync() {
let view = tempfile::tempdir().unwrap();
Expand Down
8 changes: 8 additions & 0 deletions crates/aft/src/storage_retention.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4267,6 +4267,14 @@ fn orphan_first(
/// One pass with an explicit time budget. Production always passes
/// PASS_BUDGET; tests that check the entry bound pass a budget no runner can
/// exhaust, so a slow machine cannot stop the pass by wall clock first.
#[cfg(test)]
pub(crate) fn run_pass_with_budget_for_test(
storage: &StoragePaths,
budget: Duration,
) -> SweepReport {
run_pass_with_budget(storage, &|| false, &Instant::now, budget)
}

fn run_pass_with_budget(
storage: &StoragePaths,
cancelled: &dyn Fn() -> bool,
Expand Down
45 changes: 27 additions & 18 deletions crates/aft/src/views/callgraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ use crate::callgraph_store::join::{CallgraphBlob, ManifestBlobReader, ManifestJo
use crate::callgraph_store::ReadonlyCallGraphStore;

use super::contracts::{PlaneAdapter, PlaneError, ViewAccess};
use super::manifest_v2::{EntryV2, ManifestV2};
use super::manifest_v2::EntryV2;
use super::readiness::{plane_readiness, FillMap, PlaneReadiness, PlaneState};
use super::snapshot::{DiskState, LiveEntry, OpenGeneration, Snapshot};
use super::snapshot::{ComposedGeneration, DiskState, LiveEntry, Snapshot};
use super::{Manifest, ManifestEntry, RegularPlanes, RelPath};

/// Bump when extracted evidence, its encoding or interpretation changes. Version
Expand Down Expand Up @@ -92,7 +92,9 @@ impl ManifestBlobReader for BlobReader {

/// Project membership and compatible ready keys, not checkout paths/configuration.
/// Missing/failed callgraph members refuse construction rather than falsely ready.
pub fn project_manifest(manifest: &ManifestV2) -> Result<Manifest, PlaneError> {
pub fn project_manifest(
manifest: &super::snapshot::ComposedGeneration,
) -> Result<Manifest, PlaneError> {
if manifest.header().producers.callgraph != PRODUCER {
return Err(error("incompatible callgraph producer"));
}
Expand All @@ -102,10 +104,9 @@ pub fn project_manifest(manifest: &ManifestV2) -> Result<Manifest, PlaneError> {
EntryV2::Regular {
mode,
resolution_input,
planes,
..
} => {
let key = match &planes.callgraph {
let key = match manifest.plane_readiness(path, FamilyPlane::Callgraph) {
Some(PlaneState::Ready { key }) => Some(key.clone()),
None => None,
Some(_) => {
Expand Down Expand Up @@ -138,7 +139,7 @@ pub fn project_manifest(manifest: &ManifestV2) -> Result<Manifest, PlaneError> {

pub fn materialize(
database: &Path,
manifest: &ManifestV2,
manifest: &super::snapshot::ComposedGeneration,
reader: &impl ManifestBlobReader,
) -> Result<(), PlaneError> {
let projected = project_manifest(manifest)?;
Expand All @@ -149,7 +150,7 @@ pub fn materialize(
/// Holding a reader keeps exactly its generation pinned, including after a swap.
pub struct PinnedReader {
pub store: Arc<ReadonlyCallGraphStore>,
pub generation: Arc<OpenGeneration>,
pub generation: Arc<ComposedGeneration>,
}
#[derive(Default)]
pub struct CallgraphPlane {
Expand All @@ -167,15 +168,15 @@ impl CallgraphPlane {
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(&(
access.scope().to_string(),
snapshot.generation().name().to_string(),
snapshot.generation().resident_key(),
))
.cloned();
match reader {
Some(reader) => Ok(reader),
None => {
// A refused keyless generation will never have a resident reader.
// Preserve that refusal instead of making it look like a build.
queryable_manifest(snapshot.generation().manifest())?;
queryable_manifest(snapshot.generation())?;
Err(error("pinned callgraph reader is not resident"))
}
}
Expand All @@ -197,9 +198,9 @@ impl PlaneAdapter for CallgraphPlane {
fn open_generation(
&self,
access: &ViewAccess,
generation: &Arc<OpenGeneration>,
generation: &Arc<ComposedGeneration>,
) -> Result<(), PlaneError> {
queryable_manifest(generation.manifest())?;
queryable_manifest(generation)?;
let (root, dir): (PathBuf, PathBuf) = match access {
ViewAccess::Owner(view) => (view.root().to_path_buf(), view.view_dir().to_path_buf()),
ViewAccess::Reader {
Expand Down Expand Up @@ -227,23 +228,27 @@ impl PlaneAdapter for CallgraphPlane {
generation.name(),
)
.map_err(error)?;
self.readers
let retired = self
.readers
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.insert(
(access.scope().into(), generation.name().into()),
(access.scope().into(), generation.resident_key()),
Arc::new(PinnedReader {
store: Arc::new(store.retain_generation(generation.clone())),
generation: generation.clone(),
}),
);
drop(retired);
Ok(())
}
fn release_generation(&self, access: &ViewAccess, generation: &str) {
self.readers
let retired = self
.readers
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.remove(&(access.scope().into(), generation.into()));
drop(retired);
}
fn readiness(&self, access: &ViewAccess, snapshot: &Snapshot) -> PlaneReadiness {
if !self
Expand All @@ -252,15 +257,17 @@ impl PlaneAdapter for CallgraphPlane {
.unwrap_or_else(std::sync::PoisonError::into_inner)
.get(&(
access.scope().to_owned(),
snapshot.generation().name().to_owned(),
snapshot.generation().resident_key(),
))
.is_some_and(|reader| Arc::ptr_eq(&reader.generation, snapshot.generation()))
.is_some_and(|reader| {
reader.generation.resident_key() == snapshot.generation().resident_key()
})
{
return PlaneReadiness::Building;
}
let readiness = plane_readiness(
FamilyPlane::Callgraph,
Some(snapshot.generation().manifest()),
Some(snapshot.generation()),
snapshot,
&FillMap::default(),
&|path| self.applies_to(path),
Expand All @@ -272,7 +279,9 @@ impl PlaneAdapter for CallgraphPlane {
}
}
}
fn queryable_manifest(manifest: &ManifestV2) -> Result<Manifest, PlaneError> {
fn queryable_manifest(
manifest: &super::snapshot::ComposedGeneration,
) -> Result<Manifest, PlaneError> {
let projected = project_manifest(manifest)?;
// A disabled plane still has a valid empty derived database. It must not
// answer navigation or reachability questions as though a graph was built.
Expand Down
41 changes: 39 additions & 2 deletions crates/aft/src/views/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use crate::blob_store::v2::FamilyPlane;

use super::readiness::PlaneReadiness;
use super::registry::{ReaderRegistration, ViewRegistration};
use super::snapshot::{OpenGeneration, Snapshot};
use super::snapshot::{ComposedGeneration, Snapshot};
use super::RelPath;

/// Durability boundaries of view construction. V3-only boundaries are appended
Expand Down Expand Up @@ -208,12 +208,49 @@ pub trait PlaneAdapter: Send + Sync {
fn open_generation(
&self,
access: &ViewAccess,
generation: &Arc<OpenGeneration>,
generation: &Arc<ComposedGeneration>,
) -> Result<(), PlaneError>;

/// Open the resident state and optionally retain its actual read handle.
/// Adapters whose scope caches may be cleared while queries outlive them
/// return an owned handle; simpler adapters use the release-generation lease.
fn open_reader(
&self,
access: &ViewAccess,
generation: &Arc<ComposedGeneration>,
) -> Result<Option<super::snapshot::PlaneAttachment>, PlaneError> {
self.open_generation(access, generation)?;
Ok(None)
}

/// Bind a shared immutable reader to this bundle's query state. This runs
/// for every bundle, including cache hits, so a new binding cannot inherit
/// mutable fill state from a retired binding of the same generation.
fn bind_reader(
&self,
_access: &ViewAccess,
_generation: &Arc<ComposedGeneration>,
reader: Option<super::snapshot::PlaneAttachment>,
) -> Result<Option<super::snapshot::PlaneAttachment>, PlaneError> {
Ok(reader)
}

/// Drops resident state for a generation that no snapshot holds.
fn release_generation(&self, access: &ViewAccess, generation: &str);

/// Called only after durable pointer publication for this generation succeeded.
/// Opening draft readers must not drop live protection for unpublished fills.
/// `current` returns installed disk facts without I/O or building; sample it
/// after capturing a fill-state version so a concurrent completion is not lost.
fn generation_published(
&self,
_access: &ViewAccess,
_generation: &Arc<ComposedGeneration>,
_current: &dyn Fn() -> Snapshot,
) -> Result<(), PlaneError> {
Ok(())
}

/// This plane's readiness for the view `access` names, as of `snapshot`.
///
/// The view must be named because two views can hold the same generation
Expand Down
Loading
Loading