LeVCS/crates/levcs-store/tests/crash_matrix.rs

1558 lines
60 KiB
Rust

//! The Phase 1 crash matrix (scope 4-A3 deliverable 2).
//!
//! One row per `levcs_store::failpoints::Failpoint`, carried in
//! `tests/fixtures/phase1-failpoints.json`. Every row records the physical
//! state class the failpoint produces and the recovery outcome required of it,
//! and every assertion here compares the observed outcome against **two
//! independent derivations** that must agree:
//!
//! 1. the physical state class, through `PhysicalStateClass::required_outcome`;
//! 2. the frozen Phase 0 oracle, through
//! `levcs_protocol::oracle::append_publication_expectation`.
//!
//! # There is no catch-all match arm in this file
//!
//! Not one wildcard arm, and not one fallback binding. Contract review
//! 2026-07-24-A shipped an unsound recovery classification precisely because a
//! catch-all left most outcomes unasserted, and the scope 5 charter directs the
//! adversarial reviewer to grep for one and fail the gate if it exists. That
//! grep must come back empty here, so this paragraph deliberately does not
//! spell the token out either — a comment that names it is a false positive
//! that trains a reviewer to skip the real hit. Every name-to-value mapping in
//! the harness is a search over an enumerated set for the same reason: a
//! `match` with a fallback would give an unknown name a silent default.
//!
//! # What Wave A asserts, and what it does not
//!
//! Wave A asserts the physical-state-class and `recovery_outcome` halves only.
//! `shard_poisoned`, `immediate_status`, `acknowledgment_allowed`, and
//! `later_append_allowed_before_recovery` need a status root, a sequencer, and
//! an acknowledgment path — none of which exist below `engine.rs`. They are
//! equally unassertable for all nine Wave A rows, so they do not distinguish
//! any row from any other, and the fixture records that rather than leaving it
//! to be inferred. Wave B re-asserts every row's complete
//! `FailpointExpectation` through `StoreEngine::submit`.
#![cfg(all(feature = "failpoints", feature = "store-internals"))]
mod support;
use levcs_protocol::oracle::{RecoveredTailFact, RecoveryOutcome};
use levcs_store::failpoints::{Failpoint, Wave};
use levcs_store::options::StoreOptions;
use levcs_store::types::StoreError;
use levcs_store::StoreEngine;
use support::group_model::{
canonical_group_expectation, group_failpoint_expectation, oracle_recovery_outcome,
outcome_admits, physical_state_class, verify_adopted_prefix, victim_placement,
AdoptedPrefixExpectation, PhysicalStateClass, PrefixViolation, VictimPlacement,
};
use support::harness;
/// Group size for the driven rows. Larger than one so the contiguous-prefix
/// contract is actually exercised: at `group_len == 1` a per-frame model and a
/// prefix model are indistinguishable, which is the whole reason ruling 9.3
/// insisted on the prefix form.
const DRIVEN_GROUP_LEN: usize = 4;
/// A committed group appended before the victim group, so every driven row
/// also proves that an already-fenced prefix survives the crash.
const PRIOR_GROUP_LEN: usize = 2;
// ===========================================================================
// Fixture agreement — runs today, no dependency on A1 or B1
// ===========================================================================
#[test]
fn the_fixture_carries_exactly_one_row_per_failpoint_in_registry_order() {
let fixture = harness::load_fixture();
let fixture_names: Vec<&str> = fixture.rows.iter().map(|r| r.failpoint.as_str()).collect();
let registry_names: Vec<&str> = Failpoint::ALL.iter().map(|p| p.name()).collect();
assert_eq!(
fixture_names, registry_names,
"the crash matrix must be one row per failpoint, in the registry's own \
order, so a new failpoint is a fixture diff rather than a silent hole"
);
}
#[test]
fn every_row_carries_its_wave_and_the_partition_matches_the_pinned_one() {
let fixture = harness::load_fixture();
for row in &fixture.rows {
let resolved = harness::resolve(row);
assert_eq!(
row.wave,
harness::wave_name(resolved.point.wave()),
"row {} disagrees with the wave partition pinned by name in \
failpoints.rs::Failpoint::wave",
row.failpoint
);
}
}
#[test]
fn the_two_independent_derivations_agree_for_every_row() {
let fixture = harness::load_fixture();
for row in &fixture.rows {
let resolved = harness::resolve(row);
// Derivation 1: the physical state class.
let from_class = resolved.class.required_outcome();
// Derivation 2: the frozen Phase 0 oracle.
let from_oracle = oracle_recovery_outcome(resolved.point);
assert_eq!(
from_class,
from_oracle,
"row {}: the physical state class {} implies {}, but the frozen \
oracle says {}. Two derivations that disagree means one of them is \
wrong, and neither is allowed to be adjusted to match the other \
without a recorded contract review.",
row.failpoint,
resolved.class.name(),
harness::recovery_outcome_name(from_class),
harness::recovery_outcome_name(from_oracle)
);
assert_eq!(
resolved.required_outcome, from_class,
"row {}: the recorded required_outcome must equal the class-derived one",
row.failpoint
);
}
}
#[test]
fn the_class_table_and_the_fixture_agree_on_every_row() {
let fixture = harness::load_fixture();
for row in &fixture.rows {
let resolved = harness::resolve(row);
assert_eq!(
resolved.class,
physical_state_class(resolved.point),
"row {} records a physical state class the model does not derive",
row.failpoint
);
assert_eq!(
resolved.placement,
victim_placement(resolved.class),
"row {}: victim placement is a property of the physical state class \
and may not be chosen per row",
row.failpoint
);
}
}
#[test]
fn wave_a_rows_carry_a_drive_plan_and_wave_b_rows_carry_a_submit_plan() {
let fixture = harness::load_fixture();
let mut contention_rows = Vec::new();
for row in &fixture.rows {
let resolved = harness::resolve(row);
match resolved.point.wave() {
Wave::A => {
let plan = row.drive.as_ref().unwrap_or_else(|| {
panic!("Wave A row {} must say how it is driven", row.failpoint)
});
assert!(!plan.action.is_empty(), "row {} action", row.failpoint);
assert!(!plan.fault.is_empty(), "row {} fault", row.failpoint);
assert!(
row.submit.is_none(),
"row {} is Wave A: it is driven through drive.rs, and claiming a \
submit plan as well would mean two paths assert the same row \
without either being the one under test",
row.failpoint
);
}
Wave::B => {
assert!(
row.drive.is_none(),
"row {} needs an engine and must not claim a drive plan",
row.failpoint
);
let plan = row.submit.as_ref().unwrap_or_else(|| {
panic!(
"Wave B row {} must say which actions drive it through \
StoreEngine::submit",
row.failpoint
)
});
assert!(
!plan.actions.is_empty(),
"row {}: a Wave B row with no action is a pending row without the \
word",
row.failpoint
);
for action in &plan.actions {
assert!(
action == "fail" || action == "panic",
"row {}: the submit path can express Fail and Panic. HardExit \
needs a parent process and Continue is not a fault; naming \
either here would describe a run the harness cannot make: {action:?}",
row.failpoint
);
}
if plan.requires_root_cas_contention {
contention_rows.push(row.failpoint.clone());
}
}
}
assert!(
row.rationale.len() > 20,
"row {} must explain why its class is what it is",
row.failpoint
);
}
assert_eq!(
contention_rows,
vec!["DuringRootCasRetry".to_string()],
"exactly one location is reachable only after a lost committed-root CAS. A \
second row acquiring the flag would mean a failpoint had been moved inside \
the retry loop without the matrix noticing; none acquiring it would mean \
DuringRootCasRetry had been driven by a path that never retried, which is \
the state B1 disclosed and this row exists to leave."
);
}
/// The three publication-side rows must be exercised with `Panic`, by name.
///
/// Ruling `WriterPanicAfterFence` into Wave A vacated the panic coverage of the
/// publication half. A count here would not catch a row losing its `panic`
/// action, and a check over "some row panics" would be satisfied by the wrong
/// one.
#[test]
fn the_three_publication_side_rows_are_driven_with_the_panic_action() {
let fixture = harness::load_fixture();
let panicking: Vec<&str> = fixture
.rows
.iter()
.filter(|row| {
row.submit
.as_ref()
.is_some_and(|plan| plan.actions.iter().any(|action| action == "panic"))
})
.map(|row| row.failpoint.as_str())
.collect();
assert_eq!(
panicking,
vec![
"DuringCommittedRootBuild",
"BeforeRootCas",
"DuringRootCasRetry"
],
"the fixture's own wave_b_exit_conditions names these three; the rows and the \
condition must not be able to drift apart"
);
}
#[test]
fn the_fixture_records_which_halves_wave_a_asserts_and_which_it_cannot() {
let fixture = harness::load_fixture();
assert_eq!(
fixture.wave_a_asserted,
vec![
"physical_state_class".to_string(),
"recovery_outcome".to_string()
],
"Wave A asserts exactly two halves"
);
assert_eq!(
fixture.wave_a_unasserted,
vec![
"shard_poisoned".to_string(),
"immediate_status".to_string(),
"acknowledgment_allowed".to_string(),
"later_append_allowed_before_recovery".to_string(),
],
"the remaining four FailpointExpectation fields must be listed by name; \
a count would not catch one going missing"
);
assert!(
fixture
.wave_b_exit_conditions
.iter()
.any(|c| c.contains("DuringCommittedRootBuild")
&& c.contains("BeforeRootCas")
&& c.contains("DuringRootCasRetry")),
"the fixture must name the three publication-side rows that Wave B has \
to exercise with the Panic action, so the coverage vacated by ruling \
WriterPanicAfterFence into Wave A is not lost"
);
assert_eq!(
fixture.wave_b_asserted,
vec![
"physical_state_class".to_string(),
"recovery_outcome".to_string(),
"shard_poisoned".to_string(),
"immediate_status".to_string(),
"acknowledgment_allowed".to_string(),
"later_append_allowed_before_recovery".to_string(),
],
"Wave B asserts the two halves Wave A could reach plus the four it could \
not; listing them by name is what makes one going missing a diff"
);
for field in &fixture.wave_a_unasserted {
assert!(
fixture.wave_b_asserted.contains(field),
"{field} was recorded as unassertable in Wave A, so Wave B owes it. A \
field that neither wave asserts is a hole with two labels on it."
);
}
}
/// Phase 1 exit requires zero pending rows.
///
/// **Not inert any more.** It was, while `engine.rs` was a skeleton: the check
/// asserted the pending set was *non*-empty, so the obligation could not be
/// dropped by deleting rows instead of driving them. B1's engine landed and
/// scope 6.6 deliverable 2 drove all eight, so the assertion is now
/// unconditional in the other direction.
///
/// `check-phase1.sh`'s own version of this check is still inert — it fires only
/// once `engine.rs` stops containing `NotImplemented`, and engine.rs still
/// refuses startup states 1, 3, and 4 by name. So the gate would *not* catch a
/// row that regressed to pending. This test would, and that is why it does not
/// defer to the gate.
#[test]
fn the_pending_set_is_empty_at_phase_1_exit() {
let fixture = harness::load_fixture();
let pending: Vec<&str> = fixture
.rows
.iter()
.filter(|row| row.wave == harness::PENDING_WAVE_B)
.map(|row| row.failpoint.as_str())
.collect();
assert!(
pending.is_empty(),
"no crash-matrix row may be pending: {}",
pending.join(", ")
);
let driven: Vec<&str> = fixture
.rows
.iter()
.filter(|row| row.submit.is_some())
.map(|row| row.failpoint.as_str())
.collect();
let expected: Vec<&str> = Failpoint::ALL
.iter()
.filter(|point| point.wave() == Wave::B)
.map(|point| point.name())
.collect();
assert_eq!(
driven, expected,
"the eight Wave B rows are named by the registry, not by this file. \
Restating them as a list here is how a failpoint added to failpoints.rs \
becomes a fixture diff rather than a silent hole."
);
}
/// `StoreEngine::open` builds an absent root, which is why the Wave B rows no
/// longer seed one of their own.
///
/// # This test used to assert the opposite, and that is the record
///
/// While `open` refused startup state 1, the Wave B rows created their root
/// with `segment::initialize_root` and this test asserted the refusal, so that
/// the moment it stopped being true the seeding disclosure in the fixture would
/// be caught as a false statement about the harness rather than left standing.
/// B1 landed state 1; the tripwire fired; the seeding is re-pointed at
/// `StoreEngine::open` and the disclosure is retired. What is asserted here now
/// is the property that replaced it — charter item 8 in the affirmative, at the
/// altitude a consumer calls: the store these rows exercise is built by
/// production, and a regression that returned `open` to refusing would be
/// caught here rather than by every row failing for an unrelated-looking
/// reason.
#[test]
fn the_production_open_builds_the_root_the_wave_b_rows_exercise() {
let directory = tempfile::tempdir().expect("tempdir");
let root = directory.path().join("root");
let layout = levcs_store::segment::RootLayout::new(&root);
assert!(!layout.format_path().exists(), "the root must start absent");
// The same options the rows use, because an option set that differs from
// theirs would assert about a store nobody drives.
let engine = match StoreEngine::open(engine_matrix::options(&root, 1)) {
Ok(engine) => engine,
Err(StoreError::NotImplemented(reason)) => panic!(
"StoreEngine::open refuses to build an absent root again ({reason}). The Wave \
B rows create their store through it and assert that they did; a harness \
that went back to seeding its own root would be asserting the production \
submit path over a store production never built."
),
Err(other) => panic!("StoreEngine::open refused an absent root: {other:?}"),
};
assert!(
layout.format_path().exists(),
"StoreEngine::open returned without writing FORMAT"
);
drop(engine);
// And the fixture must say so, because the disclosure a reader gets is the
// fixture's, not this file's.
//
// **Exact equality, against a short stable token.** This was
// `.contains("StoreEngine::open")` against a paragraph, which is too weak
// for a tripwire: the substring survives almost any drift in what the
// paragraph means, so the assertion would keep passing on a value that had
// come to describe a different seeding path. A token has nothing to drift
// into — it either is this value or the fixture and this file disagree, and
// the disagreement is the point. The essay lives in the adjacent
// `root_seeded_by_reason`, which the loader requires non-empty, so moving
// the history out of the matched value did not make it droppable.
let fixture = harness::load_fixture();
assert_eq!(
fixture.root_seeded_by, "store_engine_open_state_1",
"the fixture must record, as an exact token, that the Wave B rows build their \
root through StoreEngine::open on an absent path — startup state 1, the \
production entry point"
);
assert!(
fixture
.root_seeded_by_reason
.contains("segment::initialize_root"),
"the reason field carries the history the token no longer does: that the rows \
once seeded their own root because open refused state 1, and that the \
weakening is closed rather than re-worded"
);
}
// ===========================================================================
// The group-aware failpoint model (deliverable 3)
// ===========================================================================
/// Ruling 9.3's condition: the degeneracy proof must hold for **every**
/// physical state class, not only the torn one.
///
/// Iterating every failpoint covers every class by construction, and the
/// companion test below proves the coverage rather than assuming it.
#[test]
fn the_group_model_degenerates_to_the_frozen_oracle_at_group_len_one() {
for point in Failpoint::ALL {
let expectation = canonical_group_expectation(*point, 1);
let modelled = expectation.outcome_for_frame(0);
let oracle = oracle_recovery_outcome(*point);
assert_eq!(
modelled,
oracle,
"{}: at group_len == 1 the layered model must reduce to the frozen \
oracle. class {}, window [{}, {}], victim {}",
point.name(),
expectation.class.name(),
expectation.min_prefix,
expectation.max_prefix,
expectation.victim_index
);
assert_eq!(
expectation.victim_outcome(),
oracle,
"{}: the victim's outcome must also reduce to the oracle",
point.name()
);
}
}
#[test]
fn every_physical_state_class_is_witnessed_by_some_failpoint() {
for class in PhysicalStateClass::ALL {
let witnesses: Vec<&str> = Failpoint::ALL
.iter()
.filter(|p| physical_state_class(**p) == *class)
.map(|p| p.name())
.collect();
assert!(
!witnesses.is_empty(),
"physical state class {} has no failpoint, so the degeneracy proof \
above would not cover it",
class.name()
);
}
for class in PhysicalStateClass::RULING_9_3_CLASSES {
assert!(
PhysicalStateClass::ALL.contains(class),
"ruling 9.3 names a class the model does not have: {}",
class.name()
);
}
}
/// The operative half of the contract: nothing at or after the victim is ever
/// committed, for every victim position and every group size.
#[test]
fn the_model_never_permits_committing_at_or_after_the_victim() {
for point in Failpoint::ALL {
for group_len in 1..=12usize {
for victim_index in 0..=group_len {
let expectation = group_failpoint_expectation(*point, victim_index, group_len);
expectation.check_invariants();
for frame in victim_index..group_len {
assert_eq!(
expectation.outcome_for_frame(frame),
RecoveryOutcome::AbsentRetriable,
"{}: frame {frame} is at or after victim {victim_index} in a \
group of {group_len} and must never be committed",
point.name()
);
}
}
}
}
}
/// A per-frame formulation would accept this image. The prefix formulation
/// must not: recovery step 6 stops at the first incomplete frame
/// unconditionally, even when a later region holds a syntactically complete,
/// checksum-valid frame.
#[test]
fn adopting_a_valid_frame_after_a_hole_is_a_violation() {
let expectation = group_failpoint_expectation(Failpoint::DuringFrameWriteTorn, 3, 4);
// Frames 100, 101 adopted, 102 discarded (torn), 103 adopted anyway.
let observed = [100u64, 101, 103];
let violation = verify_adopted_prefix(&observed, 100, &expectation)
.expect_err("adopting past a hole must be rejected");
assert_eq!(
violation,
PrefixViolation::AdoptedPastAHole {
expected_next: 102,
observed: 103
},
"the violation must name the hole, not merely report that something is wrong"
);
}
#[test]
fn verify_adopted_prefix_accepts_exactly_the_window_and_nothing_outside_it() {
let group_len = 6usize;
for point in Failpoint::ALL {
let placement = victim_placement(physical_state_class(*point));
let victim = placement.index(group_len);
let expectation = group_failpoint_expectation(*point, victim, group_len);
for prefix in 0..=group_len {
let observed: Vec<u64> = (0..prefix as u64).map(|i| 500 + i).collect();
let result = verify_adopted_prefix(&observed, 500, &expectation);
let legal = prefix >= expectation.min_prefix && prefix <= expectation.max_prefix;
assert_eq!(
result.is_ok(),
legal,
"{}: prefix {prefix} against window [{}, {}] classified wrongly: {result:?}",
point.name(),
expectation.min_prefix,
expectation.max_prefix
);
}
}
}
#[test]
fn a_non_ascending_or_wrongly_started_adoption_set_is_not_a_prefix() {
let expectation = group_failpoint_expectation(Failpoint::AfterFrameWrite, 4, 4);
assert_eq!(
verify_adopted_prefix(&[7, 7], 7, &expectation).expect_err("duplicates"),
PrefixViolation::NotAscendingUnique {
observed: vec![7, 7]
}
);
assert_eq!(
verify_adopted_prefix(&[9, 10], 7, &expectation).expect_err("wrong start"),
PrefixViolation::WrongStart {
expected: 7,
observed: 9
}
);
}
#[test]
fn the_fenced_classes_pin_the_prefix_rather_than_leaving_a_window() {
for point in Failpoint::ALL {
let class = physical_state_class(*point);
let expectation = canonical_group_expectation(*point, 8);
let pinned = expectation.min_prefix == expectation.max_prefix;
let expected_pinned = match class {
PhysicalStateClass::NoBytes => true,
PhysicalStateClass::PartialFrame => false,
PhysicalStateClass::WholeFrameUnfenced => false,
PhysicalStateClass::WholeFrameFenced => true,
PhysicalStateClass::WholeFrameFencedAndPublished => true,
};
assert_eq!(
pinned,
expected_pinned,
"{}: a successful fence admits exactly one prefix, and an unfenced \
group admits a window; conflating the two is how a lost fenced \
write becomes indistinguishable from an ordinary crash",
point.name()
);
}
}
// ===========================================================================
// Driving the nine Wave A rows
// ===========================================================================
/// Which frame of the group stands in for the row when comparing against the
/// class-derived and oracle-derived outcomes.
fn representative_frame(placement: VictimPlacement, group_len: usize) -> usize {
match placement {
VictimPlacement::First => 0,
VictimPlacement::Last => group_len - 1,
VictimPlacement::GroupWide => group_len - 1,
}
}
/// The exit code the driver must produce for a given action, so that a row
/// which quietly ran to completion cannot be mistaken for a row whose fault
/// fired.
fn expected_exit_code(action: &str) -> i32 {
if action == "hard-exit" {
3
} else if action == "panic" {
101
} else if action == "fail" {
65
} else {
panic!("the fixture names an action the matrix cannot verify: {action:?}")
}
}
#[test]
fn wave_a_rows_drive_to_their_expected_recovery_outcome() {
let fixture = harness::load_fixture();
let mut driven = Vec::new();
for row in &fixture.rows {
let resolved = harness::resolve(row);
if resolved.point.wave() != Wave::A {
continue;
}
let plan = row
.drive
.as_ref()
.expect("a Wave A row carries a drive plan");
let directory = tempfile::tempdir().expect("tempdir");
let root = directory.path().join("root");
let ack_journal = directory.path().join("ack.journal");
let seed = 0x5643_5331_0000_0000u64 ^ (driven.len() as u64);
// A committed group first, so every row also proves that an
// already-fenced prefix survives the crash rather than only that the
// victim group does not.
let prior = harness::run_append(&harness::AppendRun {
root: &root,
shard: 0,
shard_count: 1,
seed,
group_len: PRIOR_GROUP_LEN,
victim: 0,
point: Failpoint::BeforeAppend,
action: "continue",
fault: "none",
ack_journal: &ack_journal,
create: true,
});
assert_eq!(
prior.exit_code,
Some(0),
"{}: the prior group must commit cleanly. stderr:\n{}",
row.failpoint,
prior.stderr
);
let prior_sequences = prior.sequences("appended_sequences");
assert_eq!(prior_sequences.len(), PRIOR_GROUP_LEN);
let victim_first_sequence = prior_sequences[PRIOR_GROUP_LEN - 1] + 1;
// Now the victim group.
let victim_index = resolved.placement.index(DRIVEN_GROUP_LEN);
let child = harness::run_append(&harness::AppendRun {
root: &root,
shard: 0,
shard_count: 1,
seed: seed ^ 0xFFFF,
group_len: DRIVEN_GROUP_LEN,
victim: victim_index,
point: resolved.point,
action: &plan.action,
fault: &plan.fault,
ack_journal: &ack_journal,
create: false,
});
assert_eq!(
child.exit_code,
Some(expected_exit_code(&plan.action)),
"{}: the injected fault did not fire the way the fixture says it does. \
stderr:\n{}",
row.failpoint,
child.stderr
);
// The parent classifies, in a fresh process with a fresh descriptor.
let reconcile = harness::run_reconcile(&root, 0, &ack_journal);
assert_eq!(
reconcile.get("recovery_ok"),
Some("true"),
"{}: production recovery refused to open the crash image. An ordinary \
crash must never produce an unopenable store (scope 3.8 step 5). \
stderr:\n{}",
row.failpoint,
reconcile.stderr
);
assert_eq!(
reconcile.get("acknowledged_loss"),
Some("0"),
"{}: an acknowledged operation is missing from the recovered store. \
Per scope 3.8 this is a hardware finding that invalidates the run, \
not a store bug to tolerate.",
row.failpoint
);
assert_eq!(
reconcile.get("torn_transactions"),
Some("0"),
"{}: recovery published a hole",
row.failpoint
);
// The whole property, not just the absence of forward gaps: a
// duplicate or a regression means the same frames were adopted twice,
// which `torn_transactions` deliberately does not count.
assert_eq!(
reconcile.get("adopted_duplicates"),
Some("0"),
"{}: recovery adopted a sequence twice ({:?})",
row.failpoint,
reconcile.get("first_sequence_fault")
);
assert_eq!(
reconcile.get("adopted_regressions"),
Some("0"),
"{}: recovery adopted a sequence out of order ({:?})",
row.failpoint,
reconcile.get("first_sequence_fault")
);
assert_eq!(
reconcile.get("prefix_contiguous"),
Some("true"),
"{}: the adopted set is not a strictly increasing contiguous run ({:?})",
row.failpoint,
reconcile.get("first_sequence_fault")
);
let adopted = reconcile.sequences("adopted_sequences");
for sequence in &prior_sequences {
assert!(
adopted.contains(sequence),
"{}: fenced-and-acknowledged sequence {sequence} from the prior \
group did not survive",
row.failpoint
);
}
let adopted_in_group: Vec<u64> = adopted
.iter()
.copied()
.filter(|s| *s >= victim_first_sequence)
.collect();
let expectation =
group_failpoint_expectation(resolved.point, victim_index, DRIVEN_GROUP_LEN);
let prefix = verify_adopted_prefix(&adopted_in_group, victim_first_sequence, &expectation)
.unwrap_or_else(|violation| {
panic!(
"{}: recovery's adoption of the victim group is not a legal \
contiguous prefix: {violation:?}",
row.failpoint
)
});
let representative = representative_frame(resolved.placement, DRIVEN_GROUP_LEN);
let observed_fact = if representative < prefix {
RecoveredTailFact::CompleteChecksumValid
} else {
RecoveredTailFact::AbsentOrTorn
};
assert_expectations_agree(&row.failpoint, resolved.point, &expectation, representative);
assert!(
outcome_admits(resolved.class.required_outcome(), observed_fact),
"{}: the class-derived expectation {} does not admit the observed \
tail fact {observed_fact:?}",
row.failpoint,
harness::recovery_outcome_name(resolved.class.required_outcome())
);
assert!(
outcome_admits(oracle_recovery_outcome(resolved.point), observed_fact),
"{}: the frozen oracle's expectation {} does not admit the observed \
tail fact {observed_fact:?}",
row.failpoint,
harness::recovery_outcome_name(oracle_recovery_outcome(resolved.point))
);
driven.push(row.failpoint.clone());
}
let expected: Vec<String> = Failpoint::ALL
.iter()
.filter(|p| p.wave() == Wave::A)
.map(|p| p.name().to_string())
.collect();
assert_eq!(
driven, expected,
"every Wave A row must actually have been driven, by name"
);
}
/// The third assertion, factored out only so the driving loop stays readable.
/// It is still three separate equalities, not one aggregate.
fn assert_expectations_agree(
name: &str,
point: Failpoint,
expectation: &AdoptedPrefixExpectation,
representative: usize,
) {
let from_model = expectation.outcome_for_frame(representative);
let from_class = physical_state_class(point).required_outcome();
let from_oracle = oracle_recovery_outcome(point);
assert_eq!(
from_model, from_class,
"{name}: the group model and the physical state class disagree"
);
assert_eq!(
from_class, from_oracle,
"{name}: the physical state class and the frozen oracle disagree"
);
}
// ===========================================================================
// Fault injection (deliverable 4)
// ===========================================================================
//
// The matrix rows above reach two of the userspace faults: `ShortWrite` drives
// `DuringFrameWriteTorn` and `FenceEio` drives `FenceFailed`. The remaining
// ones are not decoration and are exercised here individually, because plan
// §10 names them ("short writes, fsync errors, ENOSPC, corrupt index, corrupt
// segment, torn tail") and because a fault that is exposed but never fired is
// indistinguishable from one that does not work — which is exactly the state
// `sys.rs` was in until fault taking became positional.
//
// dm-flakey, block-device cache and barrier manipulation, and power cuts are
// the reviewed root-only scripts of §10 and are Phase 4/5. This is the
// userspace layer, and it is what lets the matrix run unprivileged in CI.
/// A committed, acknowledged group, so each fault test starts from a store
/// that has something real to lose.
fn seed_committed_group(root: &std::path::Path, ack: &std::path::Path, seed: u64) -> Vec<u64> {
let run = harness::run_append(&harness::AppendRun {
root,
shard: 0,
shard_count: 1,
seed,
group_len: PRIOR_GROUP_LEN,
victim: 0,
point: Failpoint::BeforeAppend,
action: "continue",
fault: "none",
ack_journal: ack,
create: true,
});
assert_eq!(
run.exit_code,
Some(0),
"the seed group must commit cleanly. stderr:\n{}",
run.stderr
);
run.sequences("appended_sequences")
}
fn generate_damage(root: &std::path::Path, arguments: &[&str]) -> std::process::Output {
std::process::Command::new(harness::DRIVER)
.arg("damage")
.arg("--root")
.arg(root)
.args(arguments)
.output()
.expect("spawning physical crash-image generator")
}
/// Wave A review finding 1, physical image 1: a manifest-referenced segment
/// whose footer remains valid while one of its frames no longer does.
///
/// This is generated from a segment the drive actually sealed and installed,
/// and the assertion is against the same `reconcile` command the matrix and
/// recovery script use. There is no harness-only validator in between. The
/// pre-fix Wave A reopen accepted this exact class by trusting only footer
/// offsets, so reverting that validation makes this test fail.
#[test]
fn production_recovery_rejects_a_corrupt_frame_in_a_sealed_segment() {
let directory = tempfile::tempdir().expect("tempdir");
let root = directory.path().join("root");
let ack = directory.path().join("ack.journal");
let seed = std::process::Command::new(harness::DRIVER)
.args(["append", "--root"])
.arg(&root)
.args([
"--shard",
"0",
"--shard-count",
"1",
"--create",
"--seed",
"49374",
"--group-len",
"2",
"--point",
"BeforeAppend",
"--action",
"continue",
"--fault",
"none",
])
.arg("--ack-journal")
.arg(&ack)
.arg("--seal")
.output()
.expect("seeding sealed segment");
assert!(
seed.status.success(),
"the source must be a production-sealed segment.\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&seed.stdout),
String::from_utf8_lossy(&seed.stderr)
);
let damage = generate_damage(
&root,
&["--kind", "sealed-frame-corruption", "--shard", "0"],
);
assert!(
damage.status.success(),
"the sealed-frame generator must produce its image.\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&damage.stdout),
String::from_utf8_lossy(&damage.stderr)
);
assert!(
String::from_utf8_lossy(&damage.stdout).contains("damage_phase=complete"),
"the generator must affirm that it fenced the mutation"
);
let reconcile = harness::run_reconcile(&root, 0, &ack);
assert_eq!(
reconcile.exit_code,
Some(65),
"production recovery must refuse the corrupt authority, not panic or \
publish it. stderr:\n{}",
reconcile.stderr
);
assert_eq!(reconcile.get("recovery_ok"), Some("false"));
assert!(
reconcile
.get("recovery_error")
.is_some_and(|error| error.contains("frame digest does not recompute")),
"the refusal must come from frame validation, not an unrelated setup \
failure: {:?}",
reconcile.get("recovery_error")
);
assert_eq!(
reconcile.get("adopted_sequences"),
None,
"a rejected recovery must publish no partial adoption result"
);
}
/// Wave A review finding 1, physical image 2: a valid journal from shard 1
/// moved beneath shard 0 of the same root.
///
/// Root UUID validation alone cannot catch this. Production recovery must bind
/// the journal header's shard index to the directory being recovered. The
/// pre-fix Wave A reopen adopted the moved journal whole, so this is the second
/// generator needed for the matrix to catch that original blocker.
#[test]
fn production_recovery_rejects_a_journal_moved_between_shards() {
let directory = tempfile::tempdir().expect("tempdir");
let root = directory.path().join("root");
let ack = directory.path().join("ack.journal");
let seed = std::process::Command::new(harness::DRIVER)
.args(["append", "--root"])
.arg(&root)
.args([
"--shard",
"1",
"--shard-count",
"2",
"--create",
"--seed",
"49375",
"--group-len",
"2",
"--point",
"BeforeAppend",
"--action",
"continue",
"--fault",
"none",
])
.arg("--ack-journal")
.arg(&ack)
.output()
.expect("seeding shard-1 journal");
assert!(
seed.status.success(),
"the source must be a production-written shard-1 journal.\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&seed.stdout),
String::from_utf8_lossy(&seed.stderr)
);
let damage = generate_damage(
&root,
&[
"--kind",
"cross-shard-journal-movement",
"--source-shard",
"1",
"--destination-shard",
"0",
],
);
assert!(
damage.status.success(),
"the cross-shard generator must produce its image.\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&damage.stdout),
String::from_utf8_lossy(&damage.stderr)
);
let reconcile = harness::run_reconcile(&root, 0, &ack);
assert_eq!(
reconcile.exit_code,
Some(65),
"production recovery must refuse the moved journal, not panic or adopt \
it. stderr:\n{}",
reconcile.stderr
);
assert_eq!(reconcile.get("recovery_ok"), Some("false"));
assert!(
reconcile
.get("recovery_error")
.is_some_and(|error| error.contains("journal belongs to shard 1, not shard 0")),
"the refusal must be the cross-shard binding check: {:?}",
reconcile.get("recovery_error")
);
assert_eq!(
reconcile.get("adopted_sequences"),
None,
"a rejected recovery must publish no partial adoption result"
);
}
#[test]
fn enospc_during_append_is_refused_and_leaves_an_openable_store() {
let directory = tempfile::tempdir().expect("tempdir");
let root = directory.path().join("root");
let ack = directory.path().join("ack.journal");
let committed = seed_committed_group(&root, &ack, 0xE05C);
let child = harness::run_append(&harness::AppendRun {
root: &root,
shard: 0,
shard_count: 1,
seed: 0x1234,
group_len: DRIVEN_GROUP_LEN,
victim: 0,
point: Failpoint::BeforeAppend,
action: "continue",
fault: "no-space",
ack_journal: &ack,
create: false,
});
assert_eq!(
child.exit_code,
Some(65),
"ENOSPC must surface as a refusal, not as a partially acknowledged \
group. stderr:\n{}",
child.stderr
);
assert_eq!(child.get("driver_phase"), Some("failed"));
let reconcile = harness::run_reconcile(&root, 0, &ack);
assert_eq!(reconcile.get("recovery_ok"), Some("true"));
assert_eq!(reconcile.get("acknowledged_loss"), Some("0"));
let adopted = reconcile.sequences("adopted_sequences");
for sequence in &committed {
assert!(
adopted.contains(sequence),
"a full device must not cost an already-fenced group its sequence {sequence}"
);
}
}
/// A read `EIO` anywhere in the reopen path is a clean refusal, never a panic
/// and never a partial adoption.
///
/// # What this test deliberately does not claim
///
/// It does **not** assert scope 3.8 step 5's rule that an `EIO` *in the tail
/// region* means "the tail ends here". `sys::Fault` is a one-shot global with
/// no call-site or offset targeting, so the first positioned read of a reopen —
/// the `CURRENT` pointer, the manifest, or the journal header — consumes the
/// armed fault, and an `EIO` reading a manifest is legitimately fatal. Arming
/// `read-eio` through `drive::faults` therefore cannot express "on the tail
/// read", and asserting the end-of-tail rule from here would have been a test
/// that passed for the wrong reason or failed for the wrong reason. It failed
/// for the wrong reason first, which is how the gap was found.
///
/// The tail-region rule is covered where it can be targeted: A2's
/// `tests/recovery_eio.rs` arms the fault around a single `scan_forward` call,
/// and `tests/recovery_tail.rs` covers the same rule with a real failing
/// descriptor and no global state. The gap in `drive::faults` is reported to
/// the lead as an interface request rather than papered over here.
///
/// What is left to assert from this seam is still worth asserting: a device
/// read error must surface as a `StoreError`, the harness must not report a
/// number derived from a failed recovery, and nothing may be silently adopted.
#[test]
fn a_read_eio_during_reopen_is_a_clean_refusal_and_never_a_partial_adoption() {
let directory = tempfile::tempdir().expect("tempdir");
let root = directory.path().join("root");
let ack = directory.path().join("ack.journal");
seed_committed_group(&root, &ack, 0xE10);
// Armed in the reconciling process, because that is where recovery runs —
// the fault registry is process-global and the writer's process is gone.
let reconcile = std::process::Command::new(harness::DRIVER)
.arg("reconcile")
.arg("--root")
.arg(&root)
.arg("--shard")
.arg("0")
.arg("--ack-journal")
.arg(&ack)
.arg("--fault")
.arg("read-eio")
.output()
.expect("spawning reconcile");
let stdout = String::from_utf8_lossy(&reconcile.stdout);
assert_eq!(
reconcile.status.code(),
Some(65),
"a device read error must be a refusal with a documented exit code, not \
a panic and not a success.\nstdout:\n{stdout}"
);
assert!(
stdout.contains("recovery_ok=false"),
"the classifier must say recovery did not complete, so no downstream \
number is derived from it.\nstdout:\n{stdout}"
);
assert!(
stdout.contains("injected EIO"),
"the refusal must name the cause; an opaque failure here reads exactly \
like corruption.\nstdout:\n{stdout}"
);
assert!(
!stdout.contains("adopted_sequences="),
"a recovery that did not complete must adopt nothing, and must not \
report an adoption set a caller could mistake for a result.\nstdout:\n{stdout}"
);
// And the store is not actually damaged: a reopen without the fault
// recovers the acknowledged group. An injected transient must not be
// indistinguishable from real corruption.
let clean = harness::run_reconcile(&root, 0, &ack);
assert_eq!(clean.get("recovery_ok"), Some("true"));
assert_eq!(clean.get("acknowledged_loss"), Some("0"));
}
#[test]
fn a_directory_fsync_failure_during_seal_cannot_unmake_an_acknowledgment() {
let directory = tempfile::tempdir().expect("tempdir");
let root = directory.path().join("root");
let ack = directory.path().join("ack.journal");
let child = std::process::Command::new(harness::DRIVER)
.args(["append", "--root"])
.arg(&root)
.args([
"--shard",
"0",
"--shard-count",
"1",
"--create",
"--seed",
"4242",
"--group-len",
"2",
"--point",
"BeforeAppend",
"--action",
"continue",
"--fault",
"none",
])
.arg("--ack-journal")
.arg(&ack)
.args(["--seal", "--seal-fault", "dir-sync-eio"])
.output()
.expect("spawning append");
let stdout = String::from_utf8_lossy(&child.stdout);
assert!(
stdout.contains("driver_phase=acknowledged"),
"the group is fenced and acknowledged before the seal is attempted; \
a seal fault must not be able to retract that.\nstdout:\n{stdout}"
);
let acknowledged: Vec<u64> = stdout
.lines()
.find_map(|line| line.strip_prefix("appended_sequences="))
.map(|value| {
value
.split(',')
.filter(|entry| !entry.is_empty())
.map(|entry| entry.parse::<u64>().expect("sequence"))
.collect()
})
.expect("appended_sequences");
let reconcile = harness::run_reconcile(&root, 0, &ack);
assert_eq!(
reconcile.get("recovery_ok"),
Some("true"),
"a failed directory fsync during seal must leave a store recovery can \
open. stderr:\n{}",
reconcile.stderr
);
assert_eq!(reconcile.get("acknowledged_loss"), Some("0"));
let adopted = reconcile.sequences("adopted_sequences");
for sequence in &acknowledged {
assert!(
adopted.contains(sequence),
"sequence {sequence} was acknowledged before the seal and must \
survive its failure"
);
}
}
/// The one poisoning condition plan §5.2 names that `drive::faults` cannot arm
/// on its own.
///
/// `sys::write_vectored_all` verifies the file position after an append and
/// refuses on a mismatch, which is the "unexpected file position" condition.
/// There is no `Fault` variant that produces it directly: `ShortWrite` returns
/// early with a durable prefix and no cursor error, which is the correct
/// modelling of a short write but leaves the cursor check itself unexercised
/// from this seam. Recorded here rather than left implicit, and reported to
/// the lead as an interface gap rather than worked around with a fake.
#[test]
fn the_unexpected_cursor_condition_has_no_armable_fault_and_that_is_recorded() {
let armable = [
"short-write-tears-victim",
"no-space",
"fence-eio",
"read-eio",
"dir-sync-eio",
];
assert_eq!(
armable.len(),
5,
"the userspace fault set is these five; an unexpected-cursor fault \
would be a sixth and does not exist in sys::Fault"
);
}
/// The driving test above is only meaningful while `drive.rs` is real.
///
/// If A1's seam ever regresses to `unimplemented!()`, the driving test would
/// fail with an opaque unwind from inside a child process. This asserts the
/// dependency directly so the failure names its cause.
#[test]
fn the_drive_seam_a_wave_a_row_needs_is_present() {
assert!(
harness::drive_seam_is_implemented(),
"crates/levcs-store/src/drive.rs is not implemented, so no Wave A row \
can be driven (scope 4-A1 deliverable 5)."
);
}
// ===========================================================================
// Driving the eight Wave B rows through StoreEngine::submit (scope 6.6 #2)
// ===========================================================================
//
// Wave A asserted two halves of each row's `FailpointExpectation`. These rows
// assert all six, because the four that were missing — `shard_poisoned`,
// `immediate_status`, `acknowledgment_allowed`, and
// `later_append_allowed_before_recovery` — are observable now that there is an
// engine, and they are the only thing that distinguishes several rows from one
// another. `AfterMarkedResolving` and `BeforeAppend` produce byte-identical
// stores; the entire difference between them lives in those four fields.
//
// Every observation goes through a method a consumer calls. None goes through
// an internal helper, a `cfg(test)` hook, or a field read. Scope 5 charter
// item 8.
#[cfg(feature = "store-privileged")]
use support::engine_matrix;
/// The one place the assertion is made, so every row is checked the same way
/// and a row cannot quietly assert fewer fields than its neighbours.
///
/// Six separate equalities, never one aggregate comparison of two structs. A
/// struct equality would report "expectation mismatch" and leave the reader to
/// diff two `Debug` renderings; contract review 2026-07-24-A is on record that
/// the field which is wrong is the one nobody looks at.
#[cfg(feature = "store-privileged")]
fn assert_full_expectation(
point: Failpoint,
class: PhysicalStateClass,
observation: &engine_matrix::RowObservation,
) {
use levcs_protocol::oracle::{append_publication_expectation, AppendFailpoint};
let row = &observation.row;
let expectation = append_publication_expectation(AppendFailpoint::from(point));
// 1. physical_state_class -> recovery_outcome, and the frozen oracle's
// recovery_outcome. The same two derivations Wave A compares, restated
// here so a Wave B row is not exempt from the agreement Wave A enforces.
assert_eq!(
class.required_outcome(),
expectation.recovery_outcome,
"{row}: the physical state class {} implies {}, but the frozen oracle says {}",
class.name(),
harness::recovery_outcome_name(class.required_outcome()),
harness::recovery_outcome_name(expectation.recovery_outcome)
);
// 2. recovery_outcome, observed by closing the engine and reopening
// through production recovery.
let fact = engine_matrix::recovered_fact(&observation.recovered, row);
assert!(
outcome_admits(expectation.recovery_outcome, fact),
"{row}: after a close and reopen through StoreEngine::open the victim reads \
back as {fact:?}, which the frozen oracle's {} does not admit. victim = {:?}, \
probe = {:?}, fences over the victim's group = {}",
harness::recovery_outcome_name(expectation.recovery_outcome),
observation.victim,
observation.probe,
observation.fences_during_victim()
);
// 3. shard_poisoned — the store naming itself poisoned, by variant.
assert_eq!(
observation.shard_poisoned_observed(),
expectation.shard_poisoned,
"{row}: the frozen oracle says shard_poisoned = {}. victim = {:?}, probe = {:?}",
expectation.shard_poisoned,
observation.victim,
observation.probe
);
// 4. immediate_status — plan §5.1's two-root read on the live engine.
let immediate = engine_matrix::immediate_status_of(&observation.immediate, row);
assert_eq!(
engine_matrix::immediate_status_name(immediate),
engine_matrix::immediate_status_name(expectation.immediate_status),
"{row}: transaction_status disagrees with the frozen oracle"
);
// 5. acknowledgment_allowed — a receipt obtainable through a consumer path.
assert_eq!(
observation.acknowledgment_allowed_observed(),
expectation.acknowledgment_allowed,
"{row}: the frozen oracle says acknowledgment_allowed = {}. victim = {:?}",
expectation.acknowledgment_allowed,
observation.victim
);
if !expectation.acknowledgment_allowed {
assert!(
observation.victim.receipt().is_none(),
"{row}: submit returned a receipt for an operation the oracle says may not \
be acknowledged, which is an acknowledgment whatever else is true"
);
}
// 6. later_append_allowed_before_recovery — a *different* transaction on
// the same shard, before any reopen.
assert_eq!(
observation.later_append_allowed_observed(),
expectation.later_append_allowed_before_recovery,
"{row}: the frozen oracle says later_append_allowed_before_recovery = {}. \
probe = {:?}",
expectation.later_append_allowed_before_recovery,
observation.probe
);
// The transaction that committed before the fault is not collateral. Every
// row proves that too, exactly as the Wave A rows prove it for a prior
// group; a fault that costs an already-acknowledged transaction its receipt
// is acknowledged loss, whatever the row under test says.
assert!(
matches!(
observation.prior_recovered,
levcs_store::types::TransactionStatus::Committed(_)
),
"{row}: the transaction acknowledged before the fault did not survive the \
reopen: {:?}",
observation.prior_recovered
);
// The root-lock release window that used to be reported here is gone with
// the wait that produced it: `engine_matrix::reopen_after_close` now
// asserts that the *first* `StoreEngine::open` after `drop` succeeds, so
// every row in this matrix carries that assertion at production altitude
// rather than a number describing how long it had to wait (contract review
// 2026-07-28-B, commit e050b6d).
// Charter item 7: the class is checked against a syscall count, not a
// comment. A class that says no bytes were written must show no fence, and
// a class that says the group was fenced must show exactly one.
let fences = observation.fences_during_victim();
let expected_fences = match class {
PhysicalStateClass::NoBytes => 0,
PhysicalStateClass::WholeFrameFenced => 1,
PhysicalStateClass::WholeFrameFencedAndPublished => 1,
// No Wave B row produces these two: the failpoints that tear a frame or
// stop short of the fence are all Wave A and are driven through
// drive.rs. Naming them is what makes a future row that moves into one
// of these classes fail here rather than silently skip the check.
PhysicalStateClass::PartialFrame => panic!(
"{row}: a Wave B row produced a partial frame; the submit path forms whole \
frames and the tearing failpoints are Wave A"
),
PhysicalStateClass::WholeFrameUnfenced => panic!(
"{row}: a Wave B row produced an unfenced whole frame; every Wave B location \
is either before the mark or after the fence"
),
};
assert_eq!(
fences,
expected_fences,
"{row}: physical state class {} implies {expected_fences} durability fence(s) \
for this group, and DurabilityCounters reports {fences}",
class.name()
);
}
#[cfg(feature = "store-privileged")]
#[test]
fn wave_b_rows_drive_through_submit_to_their_full_failpoint_expectation() {
let fixture = harness::load_fixture();
// One token for the whole test. The failpoint and fault registries are
// process-global one-shots, and a per-row token would let another test in
// this binary arm between two rows of this one.
let serial = engine_matrix::serial();
let mut driven: Vec<String> = Vec::new();
for row in &fixture.rows {
let resolved = harness::resolve(row);
if resolved.point.wave() != Wave::B {
continue;
}
let plan = row
.submit
.as_ref()
.expect("a Wave B row carries a submit plan");
if plan.requires_root_cas_contention {
// Driven by its own test below, which has to manufacture the lost
// compare-and-swap this location sits behind.
driven.push(row.failpoint.clone());
continue;
}
for action in &plan.actions {
let action = engine_matrix::action_from_name(action)
.unwrap_or_else(|| panic!("row {}: unknown action {action:?}", row.failpoint));
let observation = engine_matrix::drive_submit_row(&serial, resolved.point, action);
assert_full_expectation(resolved.point, resolved.class, &observation);
}
driven.push(row.failpoint.clone());
}
let expected: Vec<String> = Failpoint::ALL
.iter()
.filter(|point| point.wave() == Wave::B)
.map(|point| point.name().to_string())
.collect();
assert_eq!(
driven, expected,
"every Wave B row must actually have been driven, by name"
);
}
/// The row B1 disclosed was never armed.
///
/// `DuringRootCasRetry` sits inside `publish_subtree`'s retry loop, past a
/// failed `compare_and_swap`. Submitting a transaction does not reach it; the
/// only way there is a genuinely lost CAS, which means another shard publishing
/// into the same committed root between this shard's load and its swap. The
/// driver manufactures the load, not the mechanism: eight shard writer threads
/// publish through the production path and the harness only chooses how much
/// work each publication carries into the window.
///
/// # Why this is a hard failure and not a skip
///
/// A fault-injection row that quietly does nothing when it cannot reach its
/// location is the state this row was already in — reachable in principle,
/// unarmed in practice, and green either way. If the location stops being
/// reachable, this test says so by name and by number.
#[cfg(feature = "store-privileged")]
#[test]
fn during_root_cas_retry_drives_through_a_genuinely_lost_committed_root_cas() {
use std::time::Duration;
let fixture = harness::load_fixture();
let row = fixture
.rows
.iter()
.find(|row| row.failpoint == "DuringRootCasRetry")
.expect("the fixture carries the row");
let resolved = harness::resolve(row);
let plan = row.submit.as_ref().expect("a submit plan");
assert!(
plan.requires_root_cas_contention,
"this test manufactures contention; the fixture must say the row needs it"
);
let serial = engine_matrix::serial();
for action in &plan.actions {
let action = engine_matrix::action_from_name(action)
.unwrap_or_else(|| panic!("unknown action {action:?}"));
let run = engine_matrix::drive_root_cas_retry_row(
&serial,
action,
Duration::from_secs(CONTENTION_BUDGET_SECONDS),
);
assert!(
run.anomaly.is_none(),
"DuringRootCasRetry [{}]: a shard was refused for a reason this location \
does not produce, so no transaction in this run is the row's victim: {:?}",
engine_matrix::action_name(action),
run.anomaly
);
let observation = run.observation.unwrap_or_else(|| {
panic!(
"DuringRootCasRetry [{}] did not fire in {:?} across {} submitted \
transactions on {} shards. The location is inside publish_subtree's \
retry loop; not reaching it means either that no committed-root CAS \
was ever lost — in which case the loop is unreachable and the row \
cannot be asserted from outside the engine — or that the failpoint \
has moved out of the retry. Either is a finding, and neither is a \
reason to rerun.",
engine_matrix::action_name(action),
run.elapsed,
run.attempts,
engine_matrix::CONTENTION_SHARDS
)
});
eprintln!(
"crash_matrix: DuringRootCasRetry [{}] fired after {} transactions in {:?}",
engine_matrix::action_name(action),
run.attempts,
run.elapsed
);
assert_full_expectation(resolved.point, resolved.class, &observation);
}
}
/// How long the contention driver is given per action.
///
/// Chosen from measurement rather than from taste: see the campaign recorded in
/// the B4 report. It is a ceiling on a search, not a timeout tuned until the
/// test passed.
#[cfg(feature = "store-privileged")]
const CONTENTION_BUDGET_SECONDS: u64 = 120;