0.3.16-pre.007
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-job-backfill-lib/src/persistence.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
/// Canonical entity disposition produced by one Backfill Store persistence attempt.
|
||||
#[non_exhaustive]
|
||||
@@ -13,7 +13,7 @@ pub enum BackfillEntityPersistence {
|
||||
SkippedPurged,
|
||||
/// `getTransaction` returned JSON `null`, so no canonical Store write was attempted.
|
||||
Missing,
|
||||
/// Store reported divergent canonical content for the same network-scoped transaction identity.
|
||||
/// Store durably quarantined divergent/incomparable content, or a legacy backend reported a conflict error.
|
||||
Conflict,
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub enum BackfillObservationPersistence {
|
||||
Inserted,
|
||||
/// The same deterministic acquisition observation was already durable.
|
||||
AlreadyPresent,
|
||||
/// Store intentionally recorded no observation because normal persistence skipped a purged entity or detected a conflict.
|
||||
/// Store intentionally recorded no observation because normal persistence skipped a purged entity or a legacy conflict error predated durable quarantine.
|
||||
NotRecorded,
|
||||
/// No observation existed because hydration returned `Missing` before persistence.
|
||||
NotApplicable,
|
||||
@@ -75,9 +75,9 @@ impl crate::BackfillPersistenceOutcome {
|
||||
///
|
||||
/// Available acquisitions use the existing atomic transaction-plus-observation Store contract in
|
||||
/// `Normal` mode. A durable purge is therefore respected and never rehydrated implicitly.
|
||||
/// `Missing` performs no Store write. Stable Store content conflicts are projected explicitly as
|
||||
/// [`BackfillEntityPersistence::Conflict`] rather than being silently treated as idempotent skips.
|
||||
/// Other Store failures remain errors.
|
||||
/// `Missing` performs no Store write. Durable quarantined conflicts are projected explicitly as
|
||||
/// [`BackfillEntityPersistence::Conflict`] while preserving the actual observation disposition;
|
||||
/// legacy Store conflict errors remain compatible and other Store failures remain errors.
|
||||
pub async fn persist_backfill_hydration(
|
||||
store: &ksp_store_lib::Store,
|
||||
hydration: crate::BackfillHydrationOutcome,
|
||||
@@ -170,6 +170,17 @@ fn map_store_outcome(
|
||||
) -> ksp_core_lib::Result<crate::BackfillPersistenceOutcome> {
|
||||
let entity = outcome.entity();
|
||||
let observation = outcome.observation();
|
||||
if outcome.transaction_variant() == std::option::Option::Some(ksp_store_lib::RawTransactionVariantWriteOutcome::QuarantinedConflict) {
|
||||
let projected_observation = match observation {
|
||||
ksp_store_lib::RawObservationWriteOutcome::Inserted => crate::BackfillObservationPersistence::Inserted,
|
||||
ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent => crate::BackfillObservationPersistence::AlreadyPresent,
|
||||
_ => return std::result::Result::Err(persistence_error("store.conflict_observation")),
|
||||
};
|
||||
if entity != ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent {
|
||||
return std::result::Result::Err(persistence_error("store.conflict_entity"));
|
||||
}
|
||||
return std::result::Result::Ok(crate::BackfillPersistenceOutcome::new(reference, crate::BackfillEntityPersistence::Conflict, projected_observation));
|
||||
}
|
||||
if entity == ksp_store_lib::RawEntityWriteOutcome::Inserted && observation == ksp_store_lib::RawObservationWriteOutcome::Inserted {
|
||||
return std::result::Result::Ok(crate::BackfillPersistenceOutcome::new(
|
||||
reference,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-job-backfill-lib/unit_tests/persistence.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum FakeResponse {
|
||||
@@ -143,6 +143,14 @@ fn store_outcome(
|
||||
return ksp_store_lib::RawAcquisitionWriteOutcome::new(entity, observation);
|
||||
}
|
||||
|
||||
fn store_variant_outcome(
|
||||
entity: ksp_store_lib::RawEntityWriteOutcome,
|
||||
observation: ksp_store_lib::RawObservationWriteOutcome,
|
||||
transaction_variant: ksp_store_lib::RawTransactionVariantWriteOutcome,
|
||||
) -> ksp_store_lib::RawAcquisitionWriteOutcome {
|
||||
return ksp_store_lib::RawAcquisitionWriteOutcome::with_transaction_variant(entity, observation, transaction_variant);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_007_missing_skips_store_and_preserves_network_scoped_identity() {
|
||||
let reference = match raw_reference("devnet", 1) {
|
||||
@@ -266,7 +274,33 @@ async fn pre_007_normal_backfill_respects_purged_tombstone_without_observation()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_007_store_content_conflict_is_explicit_and_not_idempotent_success() {
|
||||
async fn v0_3_16_pre_007_durable_quarantined_conflict_preserves_observation_projection() {
|
||||
let (reference, transaction, observation) = match raw_acquisition_parts("devnet", 6, 16) {
|
||||
std::option::Option::Some(parts) => parts,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let network = match raw_network("devnet") {
|
||||
std::option::Option::Some(network) => network,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let response = store_variant_outcome(
|
||||
ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent,
|
||||
ksp_store_lib::RawObservationWriteOutcome::Inserted,
|
||||
ksp_store_lib::RawTransactionVariantWriteOutcome::QuarantinedConflict,
|
||||
);
|
||||
let port = FakePersistencePort::new(network, &[FakeResponse::Outcome(response)]);
|
||||
let result = super::persist_available_with_port(&port, reference, transaction, observation).await;
|
||||
assert!(result.is_ok());
|
||||
if let std::result::Result::Ok(result) = result {
|
||||
assert_eq!(result.entity(), crate::BackfillEntityPersistence::Conflict);
|
||||
assert_eq!(result.observation(), crate::BackfillObservationPersistence::Inserted);
|
||||
assert_ne!(result.entity(), crate::BackfillEntityPersistence::AlreadyPresent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pre_007_legacy_store_content_conflict_error_is_explicit_and_not_idempotent_success() {
|
||||
let (reference, transaction, observation) = match raw_acquisition_parts("devnet", 6, 16) {
|
||||
std::option::Option::Some(parts) => parts,
|
||||
std::option::Option::None => return,
|
||||
|
||||
Reference in New Issue
Block a user