v0.3.6-pre.009-fix.001

This commit is contained in:
2026-09-01 16:17:22 +02:00
parent b861a1e3b8
commit 75b2d7e7f1
16 changed files with 366 additions and 233 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml
# version: 408
# version: 409
[workspace]
resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"]
[workspace.package]
version = "0.3.6-pre.9"
version = "0.3.6-pre.9.fix.1"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/src/checkpoint.rs
// version: 1
// version: 2
/// Opaque caller-owned checkpoint for one controlled Backfill resumption.
///
@@ -14,7 +14,7 @@ pub struct BackfillCheckpoint {
resume_before: std::option::Option<crate::BackfillSignature>,
}
impl BackfillCheckpoint {
impl crate::BackfillCheckpoint {
/// Returns the logical Job identity that owns this checkpoint.
#[must_use]
pub const fn job_id(&self) -> &ksp_job_api::JobId {
@@ -49,7 +49,7 @@ impl BackfillCheckpoint {
}
}
impl std::fmt::Debug for BackfillCheckpoint {
impl std::fmt::Debug for crate::BackfillCheckpoint {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("BackfillCheckpoint")
@@ -67,7 +67,7 @@ pub(crate) struct CompletionFrontier {
contiguous_completed: usize,
}
impl CompletionFrontier {
impl crate::CompletionFrontier {
/// Creates one empty frontier for the exact bounded candidate count.
pub(crate) fn new(candidate_count: usize) -> Self {
return Self { durable: vec![false; candidate_count], contiguous_completed: 0 };
@@ -109,7 +109,7 @@ pub(crate) fn validate_request_checkpoint(
request_job_id: &ksp_job_api::JobId,
scope_fingerprint: crate::BackfillScopeFingerprint,
scope_kind: crate::BackfillScopeKind,
checkpoint: &BackfillCheckpoint,
checkpoint: &crate::BackfillCheckpoint,
) -> ksp_core_lib::Result<()> {
if checkpoint.job_id() != request_job_id {
return std::result::Result::Err(checkpoint_error("checkpoint.job_id"));
@@ -157,9 +157,9 @@ pub(crate) fn execution_resume_offset(request: &crate::BackfillRequest, discover
pub(crate) fn checkpoint_from_frontier(
request: &crate::BackfillRequest,
discovery: &crate::BackfillDiscovery,
frontier: &CompletionFrontier,
) -> ksp_core_lib::Result<BackfillCheckpoint> {
let validation = validate_discovery_identity(request, discovery);
frontier: &crate::CompletionFrontier,
) -> ksp_core_lib::Result<crate::BackfillCheckpoint> {
let validation = crate::validate_discovery_identity(request, discovery);
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
}
@@ -168,12 +168,12 @@ pub(crate) fn checkpoint_from_frontier(
if let std::option::Option::Some(checkpoint) = request.checkpoint() {
return std::result::Result::Ok(checkpoint.clone());
}
return std::result::Result::Ok(BackfillCheckpoint::new(request.job_id().clone(), request.scope_fingerprint(), 0, std::option::Option::None));
return std::result::Result::Ok(crate::BackfillCheckpoint::new(request.job_id().clone(), request.scope_fingerprint(), 0, std::option::Option::None));
}
let (completed_prefix, resume_before) = match request.scope().kind() {
crate::BackfillScopeKind::LatestAddress => (local_prefix, std::option::Option::None),
crate::BackfillScopeKind::BeforeAddress => {
let previous = request.checkpoint().map_or(0, BackfillCheckpoint::completed_prefix);
let previous = request.checkpoint().map_or(0, crate::BackfillCheckpoint::completed_prefix);
let completed_prefix = match previous.checked_add(local_prefix) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Err(checkpoint_error("checkpoint.completed_prefix")),
@@ -195,7 +195,7 @@ pub(crate) fn checkpoint_from_frontier(
},
crate::BackfillScopeKind::AfterAddress | crate::BackfillScopeKind::ExplicitSignatures => (local_prefix, std::option::Option::None),
};
return std::result::Result::Ok(BackfillCheckpoint::new(request.job_id().clone(), request.scope_fingerprint(), completed_prefix, resume_before));
return std::result::Result::Ok(crate::BackfillCheckpoint::new(request.job_id().clone(), request.scope_fingerprint(), completed_prefix, resume_before));
}
/// Validates that one discovery result belongs to the request network and semantic scope.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/src/conversion.rs
// version: 3
// version: 4
use sha2::Digest; // rust-rules: trait-import
@@ -24,7 +24,7 @@ struct BackfillRawAcquisitionInner {
observation: ksp_store_lib::RawTransactionObservation,
}
impl BackfillRawAcquisition {
impl crate::BackfillRawAcquisition {
/// Returns the canonical RAW transaction produced from the typed Transport response.
#[must_use]
pub const fn transaction(&self) -> &ksp_store_lib::RawTransaction {
@@ -49,12 +49,12 @@ impl BackfillRawAcquisition {
#[derive(Debug)]
pub enum BackfillHydrationOutcome {
/// The RPC returned one complete transaction and conversion produced canonical RAW plus provenance.
Available(BackfillRawAcquisition),
Available(crate::BackfillRawAcquisition),
/// The RPC returned JSON `null`; only the canonical transaction identity exists and no provenance is fabricated.
Missing(ksp_store_lib::RawTransactionReference),
}
impl BackfillHydrationOutcome {
impl crate::BackfillHydrationOutcome {
/// Returns the network-scoped transaction identity represented by this hydration outcome.
#[must_use]
pub fn reference(&self) -> &ksp_store_lib::RawTransactionReference {
@@ -81,7 +81,7 @@ pub async fn hydrate_backfill_candidate(
request: &crate::BackfillRequest,
candidate: &crate::BackfillCandidate,
received_at: ksp_store_lib::RawTimestamp,
) -> ksp_core_lib::Result<BackfillHydrationOutcome> {
) -> ksp_core_lib::Result<crate::BackfillHydrationOutcome> {
let reference = canonical_reference(request, candidate);
let reference = match reference {
std::result::Result::Ok(reference) => reference,
@@ -102,7 +102,7 @@ pub async fn hydrate_backfill_candidate(
let transaction = observed.into_value();
let transaction = match transaction {
std::option::Option::Some(transaction) => transaction,
std::option::Option::None => return std::result::Result::Ok(BackfillHydrationOutcome::Missing(reference)),
std::option::Option::None => return std::result::Result::Ok(crate::BackfillHydrationOutcome::Missing(reference)),
};
let fields = CanonicalTransactionFields {
slot: transaction.slot(),
@@ -114,7 +114,7 @@ pub async fn hydrate_backfill_candidate(
};
let acquisition = convert_available_fields(request, reference, fields, provider.as_str(), endpoint.as_str(), received_at);
return match acquisition {
std::result::Result::Ok(acquisition) => std::result::Result::Ok(BackfillHydrationOutcome::Available(acquisition)),
std::result::Result::Ok(acquisition) => std::result::Result::Ok(crate::BackfillHydrationOutcome::Available(acquisition)),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
@@ -183,7 +183,7 @@ fn convert_available_fields(
provider: &str,
endpoint: &str,
received_at: ksp_store_lib::RawTimestamp,
) -> ksp_core_lib::Result<BackfillRawAcquisition> {
) -> ksp_core_lib::Result<crate::BackfillRawAcquisition> {
let block_time = convert_block_time(fields.block_time);
let block_time = match block_time {
std::result::Result::Ok(block_time) => block_time,
@@ -195,13 +195,17 @@ fn convert_available_fields(
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let hash: [u8; 32] = sha2::Sha256::digest(bytes.as_slice()).into();
let format_id = ksp_store_lib::RawFormatId::new(RAW_TRANSACTION_FORMAT_ID);
let format_id = ksp_store_lib::RawFormatId::new(crate::RAW_TRANSACTION_FORMAT_ID);
let format_id = match format_id {
std::result::Result::Ok(format_id) => format_id,
std::result::Result::Err(_) => return std::result::Result::Err(conversion_error("payload.format_id")),
};
let payload =
ksp_store_lib::RawPayload::try_new(format_id, RAW_TRANSACTION_FORMAT_VERSION, bytes.into_boxed_slice(), ksp_store_lib::RawContentHash::new(hash));
let payload = ksp_store_lib::RawPayload::try_new(
format_id,
crate::RAW_TRANSACTION_FORMAT_VERSION,
bytes.into_boxed_slice(),
ksp_store_lib::RawContentHash::new(hash),
);
let payload = match payload {
std::result::Result::Ok(payload) => payload,
std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -214,7 +218,7 @@ fn convert_available_fields(
let observation_key = observation_key(request, &reference, provider, endpoint);
let transaction = ksp_store_lib::RawTransaction::new(reference.clone(), fields.slot, block_time, payload);
let observation = ksp_store_lib::RawTransactionObservation::new(observation_key, reference, provenance);
return std::result::Result::Ok(BackfillRawAcquisition { inner: Box::new(BackfillRawAcquisitionInner { transaction, observation }) });
return std::result::Result::Ok(crate::BackfillRawAcquisition { inner: Box::new(BackfillRawAcquisitionInner { transaction, observation }) });
}
fn convert_block_time(value: std::option::Option<i64>) -> ksp_core_lib::Result<std::option::Option<ksp_store_lib::RawTimestamp>> {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/src/discovery.rs
// version: 4
// version: 5
/// Network-scoped identity of one discovered transaction candidate before canonical signature decoding.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
@@ -8,7 +8,7 @@ pub struct BackfillCandidateIdentity {
signature: crate::BackfillSignature,
}
impl BackfillCandidateIdentity {
impl crate::BackfillCandidateIdentity {
/// Creates one candidate identity from its logical network and encoded transaction signature.
#[must_use]
pub fn new(network: ksp_store_lib::RawNetworkId, signature: crate::BackfillSignature) -> Self {
@@ -31,20 +31,20 @@ impl BackfillCandidateIdentity {
/// One deterministic transaction candidate produced by bounded discovery.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BackfillCandidate {
identity: BackfillCandidateIdentity,
identity: crate::BackfillCandidateIdentity,
discovered_slot: std::option::Option<u64>,
}
impl BackfillCandidate {
impl crate::BackfillCandidate {
/// Creates one candidate from a network-scoped identity and optional discovery slot.
#[must_use]
pub fn new(identity: BackfillCandidateIdentity, discovered_slot: std::option::Option<u64>) -> Self {
pub fn new(identity: crate::BackfillCandidateIdentity, discovered_slot: std::option::Option<u64>) -> Self {
return Self { identity, discovered_slot };
}
/// Returns the network-scoped candidate identity.
#[must_use]
pub const fn identity(&self) -> &BackfillCandidateIdentity {
pub const fn identity(&self) -> &crate::BackfillCandidateIdentity {
return &self.identity;
}
@@ -70,7 +70,7 @@ pub enum BackfillDiscoveryBoundary {
AfterAnchorNotReached,
}
impl BackfillDiscoveryBoundary {
impl crate::BackfillDiscoveryBoundary {
/// Returns the stable diagnostic code for this boundary.
#[must_use]
pub const fn code(self) -> &'static str {
@@ -94,19 +94,19 @@ impl BackfillDiscoveryBoundary {
pub struct BackfillDiscovery {
network: ksp_store_lib::RawNetworkId,
scope_fingerprint: crate::BackfillScopeFingerprint,
candidates: std::vec::Vec<BackfillCandidate>,
candidates: std::vec::Vec<crate::BackfillCandidate>,
pages_fetched: usize,
boundary: BackfillDiscoveryBoundary,
boundary: crate::BackfillDiscoveryBoundary,
}
impl BackfillDiscovery {
impl crate::BackfillDiscovery {
/// Creates one internally validated bounded discovery value.
pub(crate) fn new(
network: ksp_store_lib::RawNetworkId,
scope_fingerprint: crate::BackfillScopeFingerprint,
candidates: std::vec::Vec<BackfillCandidate>,
candidates: std::vec::Vec<crate::BackfillCandidate>,
pages_fetched: usize,
boundary: BackfillDiscoveryBoundary,
boundary: crate::BackfillDiscoveryBoundary,
) -> Self {
return Self { network, scope_fingerprint, candidates, pages_fetched, boundary };
}
@@ -125,7 +125,7 @@ impl BackfillDiscovery {
/// Returns discovered candidates in deterministic processing order.
#[must_use]
pub fn candidates(&self) -> &[BackfillCandidate] {
pub fn candidates(&self) -> &[crate::BackfillCandidate] {
return self.candidates.as_slice();
}
@@ -137,7 +137,7 @@ impl BackfillDiscovery {
/// Returns the reason discovery stopped.
#[must_use]
pub const fn boundary(&self) -> BackfillDiscoveryBoundary {
pub const fn boundary(&self) -> crate::BackfillDiscoveryBoundary {
return self.boundary;
}
@@ -148,7 +148,7 @@ impl BackfillDiscovery {
}
}
impl std::fmt::Debug for BackfillDiscovery {
impl std::fmt::Debug for crate::BackfillDiscovery {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("BackfillDiscovery")
@@ -206,7 +206,7 @@ impl SignaturePageSource for ksp_onchain_transport_lib::HttpTransportPool {
pub async fn discover_backfill_candidates(
transport: &ksp_onchain_transport_lib::HttpTransportPool,
request: &crate::BackfillRequest,
) -> ksp_core_lib::Result<BackfillDiscovery> {
) -> ksp_core_lib::Result<crate::BackfillDiscovery> {
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
job_id = request.job_id().as_str(),
@@ -239,7 +239,7 @@ pub(crate) async fn discover_backfill_candidates_cancellable(
transport: &ksp_onchain_transport_lib::HttpTransportPool,
request: &crate::BackfillRequest,
cancellation: &crate::BackfillCancellationSignal,
) -> ksp_core_lib::Result<BackfillDiscovery> {
) -> ksp_core_lib::Result<crate::BackfillDiscovery> {
return discover_with_source(transport, request, std::option::Option::Some(cancellation)).await;
}
@@ -247,7 +247,7 @@ async fn discover_with_source<S>(
source: &S,
request: &crate::BackfillRequest,
cancellation: std::option::Option<&crate::BackfillCancellationSignal>,
) -> ksp_core_lib::Result<BackfillDiscovery>
) -> ksp_core_lib::Result<crate::BackfillDiscovery>
where
S: SignaturePageSource,
{
@@ -261,22 +261,22 @@ where
};
}
fn discover_explicit(request: &crate::BackfillRequest) -> ksp_core_lib::Result<BackfillDiscovery> {
fn discover_explicit(request: &crate::BackfillRequest) -> ksp_core_lib::Result<crate::BackfillDiscovery> {
let signatures = match request.scope().signatures() {
std::option::Option::Some(signatures) => signatures,
std::option::Option::None => return std::result::Result::Err(discovery_invalid("scope.signatures")),
};
let mut candidates = std::vec::Vec::with_capacity(signatures.len());
for signature in signatures {
let identity = BackfillCandidateIdentity::new(request.network().clone(), signature.clone());
candidates.push(BackfillCandidate::new(identity, std::option::Option::None));
let identity = crate::BackfillCandidateIdentity::new(request.network().clone(), signature.clone());
candidates.push(crate::BackfillCandidate::new(identity, std::option::Option::None));
}
return std::result::Result::Ok(BackfillDiscovery::new(
return std::result::Result::Ok(crate::BackfillDiscovery::new(
request.network().clone(),
request.scope_fingerprint(),
candidates,
0,
BackfillDiscoveryBoundary::ExplicitInput,
crate::BackfillDiscoveryBoundary::ExplicitInput,
));
}
@@ -284,7 +284,7 @@ async fn discover_older<S>(
source: &S,
request: &crate::BackfillRequest,
cancellation: std::option::Option<&crate::BackfillCancellationSignal>,
) -> ksp_core_lib::Result<BackfillDiscovery>
) -> ksp_core_lib::Result<crate::BackfillDiscovery>
where
S: SignaturePageSource,
{
@@ -304,10 +304,10 @@ where
let mut pages_fetched = 0_usize;
let boundary = loop {
if candidates.len() >= request.max_candidates() {
break BackfillDiscoveryBoundary::CandidateLimit;
break crate::BackfillDiscoveryBoundary::CandidateLimit;
}
if pages_fetched >= request.max_pages() {
break BackfillDiscoveryBoundary::PageLimit;
break crate::BackfillDiscoveryBoundary::PageLimit;
}
let remaining = request.max_candidates() - candidates.len();
let page_limit = std::cmp::min(request.page_size(), remaining);
@@ -345,33 +345,33 @@ where
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if seen.insert(signature.clone()) {
let identity = BackfillCandidateIdentity::new(request.network().clone(), signature);
candidates.push(BackfillCandidate::new(identity, std::option::Option::Some(entry.slot)));
let identity = crate::BackfillCandidateIdentity::new(request.network().clone(), signature);
candidates.push(crate::BackfillCandidate::new(identity, std::option::Option::Some(entry.slot)));
if candidates.len() >= request.max_candidates() {
break;
}
}
}
if page_len < page_limit {
break BackfillDiscoveryBoundary::RpcBoundary;
break crate::BackfillDiscoveryBoundary::RpcBoundary;
}
let next_before = match next_before {
std::option::Option::Some(next_before) => next_before,
std::option::Option::None => break BackfillDiscoveryBoundary::RpcBoundary,
std::option::Option::None => break crate::BackfillDiscoveryBoundary::RpcBoundary,
};
if before.as_deref() == std::option::Option::Some(next_before.as_str()) {
return std::result::Result::Err(discovery_stalled());
}
before = std::option::Option::Some(next_before.as_str().to_owned());
};
return std::result::Result::Ok(BackfillDiscovery::new(request.network().clone(), request.scope_fingerprint(), candidates, pages_fetched, boundary));
return std::result::Result::Ok(crate::BackfillDiscovery::new(request.network().clone(), request.scope_fingerprint(), candidates, pages_fetched, boundary));
}
async fn discover_after<S>(
source: &S,
request: &crate::BackfillRequest,
cancellation: std::option::Option<&crate::BackfillCancellationSignal>,
) -> ksp_core_lib::Result<BackfillDiscovery>
) -> ksp_core_lib::Result<crate::BackfillDiscovery>
where
S: SignaturePageSource,
{
@@ -385,12 +385,12 @@ where
};
let until = anchor.as_str().to_owned();
let mut before = std::option::Option::<std::string::String>::None;
let mut nearest = std::collections::VecDeque::<BackfillCandidate>::with_capacity(request.max_candidates());
let mut nearest = std::collections::VecDeque::<crate::BackfillCandidate>::with_capacity(request.max_candidates());
let mut seen = std::collections::HashSet::<crate::BackfillSignature>::new();
let mut pages_fetched = 0_usize;
let boundary = loop {
if pages_fetched >= request.max_pages() {
break BackfillDiscoveryBoundary::AfterAnchorNotReached;
break crate::BackfillDiscoveryBoundary::AfterAnchorNotReached;
}
let config = ksp_onchain_transport_lib::SolanaSignaturesForAddressConfig::new(
before.clone(),
@@ -426,26 +426,26 @@ where
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if seen.insert(signature.clone()) {
let identity = BackfillCandidateIdentity::new(request.network().clone(), signature);
nearest.push_back(BackfillCandidate::new(identity, std::option::Option::Some(entry.slot)));
let identity = crate::BackfillCandidateIdentity::new(request.network().clone(), signature);
nearest.push_back(crate::BackfillCandidate::new(identity, std::option::Option::Some(entry.slot)));
if nearest.len() > request.max_candidates() {
nearest.pop_front();
}
}
}
if page_len < request.page_size() {
break BackfillDiscoveryBoundary::RpcBoundary;
break crate::BackfillDiscoveryBoundary::RpcBoundary;
}
let next_before = match next_before {
std::option::Option::Some(next_before) => next_before,
std::option::Option::None => break BackfillDiscoveryBoundary::RpcBoundary,
std::option::Option::None => break crate::BackfillDiscoveryBoundary::RpcBoundary,
};
if before.as_deref() == std::option::Option::Some(next_before.as_str()) {
return std::result::Result::Err(discovery_stalled());
}
before = std::option::Option::Some(next_before.as_str().to_owned());
};
return std::result::Result::Ok(BackfillDiscovery::new(
return std::result::Result::Ok(crate::BackfillDiscovery::new(
request.network().clone(),
request.scope_fingerprint(),
nearest.into_iter().collect(),

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/src/execution.rs
// version: 2
// version: 3
use futures_util::StreamExt; // rust-rules: trait-import
@@ -27,7 +27,7 @@ pub struct BackfillExecutionBatch {
failure_code: std::option::Option<ksp_core_lib::ErrorCode>,
}
impl BackfillExecutionBatch {
impl crate::BackfillExecutionBatch {
/// Returns the number of candidates present in the bounded discovery result.
#[must_use]
pub const fn candidate_count(&self) -> usize {
@@ -162,7 +162,7 @@ pub(crate) struct BackfillExecutionProgress {
checkpoint: crate::BackfillCheckpoint,
}
impl BackfillExecutionProgress {
impl crate::BackfillExecutionProgress {
/// Returns the cumulative admission count.
pub(crate) const fn admitted_count(&self) -> usize {
return self.admitted_count;
@@ -243,7 +243,7 @@ pub async fn execute_backfill_discovery(
store: &ksp_store_lib::Store,
request: &crate::BackfillRequest,
discovery: &crate::BackfillDiscovery,
) -> ksp_core_lib::Result<BackfillExecutionBatch> {
) -> ksp_core_lib::Result<crate::BackfillExecutionBatch> {
let validation = crate::validate_discovery_identity(request, discovery);
if let std::result::Result::Err(error) = validation {
return std::result::Result::Err(error);
@@ -260,7 +260,7 @@ pub(crate) async fn execute_backfill_discovery_cancellable(
discovery: &crate::BackfillDiscovery,
cancellation: &crate::BackfillCancellationSignal,
publisher: &crate::BackfillRuntimePublisher,
) -> ksp_core_lib::Result<BackfillExecutionBatch> {
) -> ksp_core_lib::Result<crate::BackfillExecutionBatch> {
let processor = RuntimeCandidateProcessor { transport, store, request, cancellation: std::option::Option::Some(cancellation) };
return execute_with_processor(&processor, request, discovery, std::option::Option::Some(cancellation), std::option::Option::Some(publisher)).await;
}
@@ -311,7 +311,7 @@ async fn execute_with_processor<P>(
discovery: &crate::BackfillDiscovery,
cancellation: std::option::Option<&crate::BackfillCancellationSignal>,
publisher: std::option::Option<&crate::BackfillRuntimePublisher>,
) -> ksp_core_lib::Result<BackfillExecutionBatch>
) -> ksp_core_lib::Result<crate::BackfillExecutionBatch>
where
P: CandidateProcessor,
{
@@ -484,7 +484,7 @@ where
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(BackfillExecutionBatch {
return std::result::Result::Ok(crate::BackfillExecutionBatch {
candidate_count: discovery.candidates().len(),
admitted_count,
finished_count,
@@ -524,12 +524,12 @@ fn progress_from_state(
cancelled_count: usize,
hole_count: usize,
maximum_in_flight: usize,
) -> ksp_core_lib::Result<BackfillExecutionProgress> {
) -> ksp_core_lib::Result<crate::BackfillExecutionProgress> {
let checkpoint = match crate::checkpoint_from_frontier(request, discovery, frontier) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(BackfillExecutionProgress {
return std::result::Result::Ok(crate::BackfillExecutionProgress {
admitted_count,
finished_count,
inserted_count,
@@ -549,7 +549,7 @@ fn progress_from_state(
fn publish_progress(
publisher: std::option::Option<&crate::BackfillRuntimePublisher>,
progress: &BackfillExecutionProgress,
progress: &crate::BackfillExecutionProgress,
cancelling: bool,
draining: bool,
) -> ksp_core_lib::Result<()> {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/src/lib.rs
// version: 5
// version: 6
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -139,9 +139,5 @@ pub(crate) use self::execution::BackfillExecutionProgress;
pub(crate) use self::execution::execute_backfill_discovery_cancellable;
/// Internal concrete cancellation signal shared by discovery and execution.
pub(crate) use self::runtime::BackfillCancellationSignal;
/// Internal atomic terminal/cancellation arbitration shared by runtime tests and coordinator.
pub(crate) use self::runtime::BackfillRuntimeControl;
/// Internal execution progress publisher feeding the latest-value snapshot source.
pub(crate) use self::runtime::BackfillRuntimePublisher;
/// Internal terminal race result used by coordinator canaries.
pub(crate) use self::runtime::TerminalClaim;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/src/persistence.rs
// version: 2
// version: 3
/// Canonical entity disposition produced by one Backfill Store persistence attempt.
#[non_exhaustive]
@@ -38,16 +38,16 @@ pub enum BackfillObservationPersistence {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BackfillPersistenceOutcome {
reference: ksp_store_lib::RawTransactionReference,
entity: BackfillEntityPersistence,
observation: BackfillObservationPersistence,
entity: crate::BackfillEntityPersistence,
observation: crate::BackfillObservationPersistence,
}
impl BackfillPersistenceOutcome {
impl crate::BackfillPersistenceOutcome {
/// Creates one internally classified persistence result.
pub(crate) fn new(
reference: ksp_store_lib::RawTransactionReference,
entity: BackfillEntityPersistence,
observation: BackfillObservationPersistence,
entity: crate::BackfillEntityPersistence,
observation: crate::BackfillObservationPersistence,
) -> Self {
return Self { reference, entity, observation };
}
@@ -60,13 +60,13 @@ impl BackfillPersistenceOutcome {
/// Returns the canonical transaction persistence disposition.
#[must_use]
pub const fn entity(&self) -> BackfillEntityPersistence {
pub const fn entity(&self) -> crate::BackfillEntityPersistence {
return self.entity;
}
/// Returns the acquisition-observation persistence disposition.
#[must_use]
pub const fn observation(&self) -> BackfillObservationPersistence {
pub const fn observation(&self) -> crate::BackfillObservationPersistence {
return self.observation;
}
}
@@ -81,7 +81,7 @@ impl BackfillPersistenceOutcome {
pub async fn persist_backfill_hydration(
store: &ksp_store_lib::Store,
hydration: crate::BackfillHydrationOutcome,
) -> ksp_core_lib::Result<BackfillPersistenceOutcome> {
) -> ksp_core_lib::Result<crate::BackfillPersistenceOutcome> {
return persist_hydration_with_port(store, hydration).await;
}
@@ -112,7 +112,7 @@ impl RawTransactionPersistencePort for ksp_store_lib::Store {
}
}
async fn persist_hydration_with_port<P>(port: &P, hydration: crate::BackfillHydrationOutcome) -> ksp_core_lib::Result<BackfillPersistenceOutcome>
async fn persist_hydration_with_port<P>(port: &P, hydration: crate::BackfillHydrationOutcome) -> ksp_core_lib::Result<crate::BackfillPersistenceOutcome>
where
P: RawTransactionPersistencePort,
{
@@ -121,10 +121,10 @@ where
return std::result::Result::Err(persistence_error("store.network"));
}
return match hydration {
crate::BackfillHydrationOutcome::Missing(_) => std::result::Result::Ok(BackfillPersistenceOutcome::new(
crate::BackfillHydrationOutcome::Missing(_) => std::result::Result::Ok(crate::BackfillPersistenceOutcome::new(
reference,
BackfillEntityPersistence::Missing,
BackfillObservationPersistence::NotApplicable,
crate::BackfillEntityPersistence::Missing,
crate::BackfillObservationPersistence::NotApplicable,
)),
crate::BackfillHydrationOutcome::Available(acquisition) => {
let (transaction, observation) = acquisition.into_parts();
@@ -138,7 +138,7 @@ async fn persist_available_with_port<P>(
reference: ksp_store_lib::RawTransactionReference,
transaction: ksp_store_lib::RawTransaction,
observation: ksp_store_lib::RawTransactionObservation,
) -> ksp_core_lib::Result<BackfillPersistenceOutcome>
) -> ksp_core_lib::Result<crate::BackfillPersistenceOutcome>
where
P: RawTransactionPersistencePort,
{
@@ -153,10 +153,10 @@ where
std::result::Result::Ok(outcome) => map_store_outcome(reference, outcome),
std::result::Result::Err(error) => {
if error.code() == ksp_store_lib::ERROR_CODE_RAW_CONFLICT {
return std::result::Result::Ok(BackfillPersistenceOutcome::new(
return std::result::Result::Ok(crate::BackfillPersistenceOutcome::new(
reference,
BackfillEntityPersistence::Conflict,
BackfillObservationPersistence::NotRecorded,
crate::BackfillEntityPersistence::Conflict,
crate::BackfillObservationPersistence::NotRecorded,
));
}
std::result::Result::Err(error)
@@ -167,35 +167,35 @@ where
fn map_store_outcome(
reference: ksp_store_lib::RawTransactionReference,
outcome: ksp_store_lib::RawAcquisitionWriteOutcome,
) -> ksp_core_lib::Result<BackfillPersistenceOutcome> {
) -> ksp_core_lib::Result<crate::BackfillPersistenceOutcome> {
let entity = outcome.entity();
let observation = outcome.observation();
if entity == ksp_store_lib::RawEntityWriteOutcome::Inserted && observation == ksp_store_lib::RawObservationWriteOutcome::Inserted {
return std::result::Result::Ok(BackfillPersistenceOutcome::new(
return std::result::Result::Ok(crate::BackfillPersistenceOutcome::new(
reference,
BackfillEntityPersistence::Inserted,
BackfillObservationPersistence::Inserted,
crate::BackfillEntityPersistence::Inserted,
crate::BackfillObservationPersistence::Inserted,
));
}
if entity == ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent && observation == ksp_store_lib::RawObservationWriteOutcome::Inserted {
return std::result::Result::Ok(BackfillPersistenceOutcome::new(
return std::result::Result::Ok(crate::BackfillPersistenceOutcome::new(
reference,
BackfillEntityPersistence::AlreadyPresent,
BackfillObservationPersistence::Inserted,
crate::BackfillEntityPersistence::AlreadyPresent,
crate::BackfillObservationPersistence::Inserted,
));
}
if entity == ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent && observation == ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent {
return std::result::Result::Ok(BackfillPersistenceOutcome::new(
return std::result::Result::Ok(crate::BackfillPersistenceOutcome::new(
reference,
BackfillEntityPersistence::AlreadyPresent,
BackfillObservationPersistence::AlreadyPresent,
crate::BackfillEntityPersistence::AlreadyPresent,
crate::BackfillObservationPersistence::AlreadyPresent,
));
}
if entity == ksp_store_lib::RawEntityWriteOutcome::SkippedPurged && observation == ksp_store_lib::RawObservationWriteOutcome::NotRecorded {
return std::result::Result::Ok(BackfillPersistenceOutcome::new(
return std::result::Result::Ok(crate::BackfillPersistenceOutcome::new(
reference,
BackfillEntityPersistence::SkippedPurged,
BackfillObservationPersistence::NotRecorded,
crate::BackfillEntityPersistence::SkippedPurged,
crate::BackfillObservationPersistence::NotRecorded,
));
}
return std::result::Result::Err(persistence_error("store.outcome"));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/src/request.rs
// version: 3
// version: 4
use sha2::Digest; // rust-rules: trait-import
@@ -25,7 +25,7 @@ pub enum BackfillCommitment {
Finalized,
}
impl BackfillCommitment {
impl crate::BackfillCommitment {
/// Returns the stable Backfill commitment code.
#[must_use]
pub const fn code(self) -> &'static str {
@@ -52,7 +52,7 @@ impl BackfillCommitment {
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BackfillSignature(std::string::String);
impl BackfillSignature {
impl crate::BackfillSignature {
/// Creates one bounded Base58-shaped signature text.
pub fn new(value: impl std::convert::Into<std::string::String>) -> ksp_core_lib::Result<Self> {
let value = value.into();
@@ -74,7 +74,7 @@ impl BackfillSignature {
}
}
impl std::fmt::Debug for BackfillSignature {
impl std::fmt::Debug for crate::BackfillSignature {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.write_str("BackfillSignature(..)");
}
@@ -93,7 +93,7 @@ pub enum BackfillScopeKind {
ExplicitSignatures,
}
impl BackfillScopeKind {
impl crate::BackfillScopeKind {
/// Returns the stable scope code used by diagnostics and scope fingerprinting.
#[must_use]
pub const fn code(self) -> &'static str {
@@ -109,9 +109,9 @@ impl BackfillScopeKind {
#[derive(Clone, Eq, PartialEq)]
enum BackfillScopeValue {
LatestAddress { address: ksp_core_lib::Pubkey },
BeforeAddress { address: ksp_core_lib::Pubkey, anchor: BackfillSignature },
AfterAddress { address: ksp_core_lib::Pubkey, anchor: BackfillSignature },
ExplicitSignatures { signatures: std::vec::Vec<BackfillSignature> },
BeforeAddress { address: ksp_core_lib::Pubkey, anchor: crate::BackfillSignature },
AfterAddress { address: ksp_core_lib::Pubkey, anchor: crate::BackfillSignature },
ExplicitSignatures { signatures: std::vec::Vec<crate::BackfillSignature> },
}
/// Validated bounded discovery scope for one historical Backfill Job.
@@ -120,7 +120,7 @@ pub struct BackfillScope {
value: BackfillScopeValue,
}
impl BackfillScope {
impl crate::BackfillScope {
/// Creates a scope starting at the newest known history for one address.
#[must_use]
pub const fn latest_address(address: ksp_core_lib::Pubkey) -> Self {
@@ -129,18 +129,18 @@ impl BackfillScope {
/// Creates a scope reading history older than one exclusive address anchor.
#[must_use]
pub fn before_address(address: ksp_core_lib::Pubkey, anchor: BackfillSignature) -> Self {
pub fn before_address(address: ksp_core_lib::Pubkey, anchor: crate::BackfillSignature) -> Self {
return Self { value: BackfillScopeValue::BeforeAddress { address, anchor } };
}
/// Creates a scope reading the bounded newer history closest to one exclusive address anchor.
#[must_use]
pub fn after_address(address: ksp_core_lib::Pubkey, anchor: BackfillSignature) -> Self {
pub fn after_address(address: ksp_core_lib::Pubkey, anchor: crate::BackfillSignature) -> Self {
return Self { value: BackfillScopeValue::AfterAddress { address, anchor } };
}
/// Creates an explicit signature scope with stable first-occurrence deduplication.
pub fn explicit_signatures(signatures: std::vec::Vec<BackfillSignature>) -> ksp_core_lib::Result<Self> {
pub fn explicit_signatures(signatures: std::vec::Vec<crate::BackfillSignature>) -> ksp_core_lib::Result<Self> {
if signatures.is_empty() || signatures.len() > crate::MAX_BACKFILL_CANDIDATES {
return std::result::Result::Err(request_error("scope.signatures"));
}
@@ -159,12 +159,12 @@ impl BackfillScope {
/// Returns the stable category of this scope.
#[must_use]
pub const fn kind(&self) -> BackfillScopeKind {
pub const fn kind(&self) -> crate::BackfillScopeKind {
return match &self.value {
BackfillScopeValue::LatestAddress { .. } => BackfillScopeKind::LatestAddress,
BackfillScopeValue::BeforeAddress { .. } => BackfillScopeKind::BeforeAddress,
BackfillScopeValue::AfterAddress { .. } => BackfillScopeKind::AfterAddress,
BackfillScopeValue::ExplicitSignatures { .. } => BackfillScopeKind::ExplicitSignatures,
BackfillScopeValue::LatestAddress { .. } => crate::BackfillScopeKind::LatestAddress,
BackfillScopeValue::BeforeAddress { .. } => crate::BackfillScopeKind::BeforeAddress,
BackfillScopeValue::AfterAddress { .. } => crate::BackfillScopeKind::AfterAddress,
BackfillScopeValue::ExplicitSignatures { .. } => crate::BackfillScopeKind::ExplicitSignatures,
};
}
@@ -181,7 +181,7 @@ impl BackfillScope {
/// Returns the exclusive anchor used by before/after scopes.
#[must_use]
pub const fn anchor(&self) -> std::option::Option<&BackfillSignature> {
pub const fn anchor(&self) -> std::option::Option<&crate::BackfillSignature> {
return match &self.value {
BackfillScopeValue::BeforeAddress { anchor, .. } | BackfillScopeValue::AfterAddress { anchor, .. } => std::option::Option::Some(anchor),
BackfillScopeValue::LatestAddress { .. } | BackfillScopeValue::ExplicitSignatures { .. } => std::option::Option::None,
@@ -190,7 +190,7 @@ impl BackfillScope {
/// Returns the stable deduplicated explicit signature list, when this is an explicit scope.
#[must_use]
pub fn signatures(&self) -> std::option::Option<&[BackfillSignature]> {
pub fn signatures(&self) -> std::option::Option<&[crate::BackfillSignature]> {
return match &self.value {
BackfillScopeValue::ExplicitSignatures { signatures } => std::option::Option::Some(signatures.as_slice()),
BackfillScopeValue::LatestAddress { .. } | BackfillScopeValue::BeforeAddress { .. } | BackfillScopeValue::AfterAddress { .. } => {
@@ -200,7 +200,7 @@ impl BackfillScope {
}
}
impl std::fmt::Debug for BackfillScope {
impl std::fmt::Debug for crate::BackfillScope {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = formatter.debug_struct("BackfillScope");
debug.field("kind", &self.kind());
@@ -223,7 +223,7 @@ impl std::fmt::Debug for BackfillScope {
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct BackfillScopeFingerprint([u8; 32]);
impl BackfillScopeFingerprint {
impl crate::BackfillScopeFingerprint {
/// Returns the exact deterministic fingerprint bytes.
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
@@ -231,7 +231,7 @@ impl BackfillScopeFingerprint {
}
}
impl std::fmt::Debug for BackfillScopeFingerprint {
impl std::fmt::Debug for crate::BackfillScopeFingerprint {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.write_str("BackfillScopeFingerprint(..)");
}
@@ -243,26 +243,26 @@ pub struct BackfillRequest {
job_id: ksp_job_api::JobId,
network: ksp_store_lib::RawNetworkId,
role: ksp_onchain_transport_lib::HttpRoleName,
commitment: BackfillCommitment,
scope: BackfillScope,
commitment: crate::BackfillCommitment,
scope: crate::BackfillScope,
page_size: usize,
max_pages: usize,
max_candidates: usize,
hydration_concurrency: usize,
min_context_slot: std::option::Option<u64>,
scope_fingerprint: BackfillScopeFingerprint,
scope_fingerprint: crate::BackfillScopeFingerprint,
checkpoint: std::option::Option<crate::BackfillCheckpoint>,
}
impl BackfillRequest {
impl crate::BackfillRequest {
/// Creates and validates one fully explicit bounded Backfill request.
#[allow(clippy::too_many_arguments)]
pub fn new(
job_id: ksp_job_api::JobId,
network: ksp_store_lib::RawNetworkId,
role: ksp_onchain_transport_lib::HttpRoleName,
commitment: BackfillCommitment,
scope: BackfillScope,
commitment: crate::BackfillCommitment,
scope: crate::BackfillScope,
page_size: usize,
max_pages: usize,
max_candidates: usize,
@@ -284,7 +284,7 @@ impl BackfillRequest {
if role.as_str().is_empty() || role.as_str().trim() != role.as_str() {
return std::result::Result::Err(request_error("role"));
}
if scope.kind() == BackfillScopeKind::ExplicitSignatures {
if scope.kind() == crate::BackfillScopeKind::ExplicitSignatures {
if min_context_slot.is_some() {
return std::result::Result::Err(request_error("min_context_slot"));
}
@@ -333,13 +333,13 @@ impl BackfillRequest {
/// Returns the narrowed commitment used by discovery and hydration.
#[must_use]
pub const fn commitment(&self) -> BackfillCommitment {
pub const fn commitment(&self) -> crate::BackfillCommitment {
return self.commitment;
}
/// Returns the validated discovery scope.
#[must_use]
pub const fn scope(&self) -> &BackfillScope {
pub const fn scope(&self) -> &crate::BackfillScope {
return &self.scope;
}
@@ -378,7 +378,7 @@ impl BackfillRequest {
/// Transport role, provider, endpoint and protocol are deliberately excluded. They describe
/// acquisition provenance, not transaction or scope identity.
#[must_use]
pub const fn scope_fingerprint(&self) -> BackfillScopeFingerprint {
pub const fn scope_fingerprint(&self) -> crate::BackfillScopeFingerprint {
return self.scope_fingerprint;
}
@@ -399,7 +399,7 @@ impl BackfillRequest {
}
}
impl std::fmt::Debug for BackfillRequest {
impl std::fmt::Debug for crate::BackfillRequest {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("BackfillRequest")
@@ -421,13 +421,13 @@ impl std::fmt::Debug for BackfillRequest {
fn fingerprint_scope(
network: &ksp_store_lib::RawNetworkId,
commitment: BackfillCommitment,
scope: &BackfillScope,
commitment: crate::BackfillCommitment,
scope: &crate::BackfillScope,
page_size: usize,
max_pages: usize,
max_candidates: usize,
min_context_slot: std::option::Option<u64>,
) -> BackfillScopeFingerprint {
) -> crate::BackfillScopeFingerprint {
let mut hasher = sha2::Sha256::new();
hasher.update(b"ksp.job.backfill.scope.v1\0");
hash_bytes(&mut hasher, network.as_str().as_bytes());
@@ -455,7 +455,7 @@ fn fingerprint_scope(
std::option::Option::None => hasher.update([0_u8]),
}
let bytes: [u8; 32] = hasher.finalize().into();
return BackfillScopeFingerprint(bytes);
return crate::BackfillScopeFingerprint(bytes);
}
fn hash_bytes(hasher: &mut sha2::Sha256, value: &[u8]) {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/src/runtime.rs
// version: 2
// version: 3
/// Stable Job kind code used by the concrete historical RAW transaction Backfill runtime.
pub const BACKFILL_JOB_KIND_CODE: &str = "solana.raw_transaction.backfill";
@@ -26,7 +26,7 @@ pub enum BackfillJobPhase {
Finished,
}
impl BackfillJobPhase {
impl crate::BackfillJobPhase {
/// Returns the stable safe code for this concrete runtime phase.
#[must_use]
pub const fn code(self) -> &'static str {
@@ -43,7 +43,7 @@ impl BackfillJobPhase {
/// Complete safe latest-value snapshot of one concrete historical RAW transaction Backfill Job.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BackfillJobSnapshot {
phase: BackfillJobPhase,
phase: crate::BackfillJobPhase,
scope_kind: crate::BackfillScopeKind,
discovery_boundary: std::option::Option<crate::BackfillDiscoveryBoundary>,
candidates_selected: usize,
@@ -64,10 +64,10 @@ pub struct BackfillJobSnapshot {
failure_code: std::option::Option<ksp_core_lib::ErrorCode>,
}
impl BackfillJobSnapshot {
impl crate::BackfillJobSnapshot {
fn initial(request: &crate::BackfillRequest) -> Self {
return Self {
phase: BackfillJobPhase::Created,
phase: crate::BackfillJobPhase::Created,
scope_kind: request.scope().kind(),
discovery_boundary: std::option::Option::None,
candidates_selected: 0,
@@ -91,7 +91,7 @@ impl BackfillJobSnapshot {
/// Returns the concrete execution phase represented by this snapshot.
#[must_use]
pub const fn phase(&self) -> BackfillJobPhase {
pub const fn phase(&self) -> crate::BackfillJobPhase {
return self.phase;
}
@@ -207,11 +207,11 @@ impl BackfillJobSnapshot {
/// Cloneable runtime-neutral-facing latest-value source for concrete Backfill snapshots.
#[derive(Clone)]
pub struct BackfillSnapshotSource {
receiver: tokio::sync::watch::Receiver<ksp_job_api::JobNotification<BackfillJobSnapshot>>,
receiver: tokio::sync::watch::Receiver<ksp_job_api::JobNotification<crate::BackfillJobSnapshot>>,
}
impl ksp_job_api::JobSnapshotSource for BackfillSnapshotSource {
type Snapshot = BackfillJobSnapshot;
impl ksp_job_api::JobSnapshotSource for crate::BackfillSnapshotSource {
type Snapshot = crate::BackfillJobSnapshot;
fn current(&self) -> ksp_job_api::JobNotification<Self::Snapshot> {
return self.receiver.borrow().clone();
@@ -234,7 +234,7 @@ impl ksp_job_api::JobSnapshotSource for BackfillSnapshotSource {
}
}
impl std::fmt::Debug for BackfillSnapshotSource {
impl std::fmt::Debug for crate::BackfillSnapshotSource {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let current = self.receiver.borrow();
return formatter.debug_struct("BackfillSnapshotSource").field("sequence", &current.sequence()).field("state", &current.state()).finish();
@@ -245,10 +245,10 @@ impl std::fmt::Debug for BackfillSnapshotSource {
#[derive(Clone)]
pub struct BackfillJobHandle {
control: BackfillRuntimeControl,
snapshots: BackfillSnapshotSource,
snapshots: crate::BackfillSnapshotSource,
}
impl BackfillJobHandle {
impl crate::BackfillJobHandle {
/// Requests cooperative cancellation and returns `true` only when accepted before terminal publication.
#[must_use]
pub fn cancel(&self) -> bool {
@@ -257,7 +257,7 @@ impl BackfillJobHandle {
/// Returns an independent latest-value snapshot source for one listener.
#[must_use]
pub fn snapshots(&self) -> BackfillSnapshotSource {
pub fn snapshots(&self) -> crate::BackfillSnapshotSource {
return self.snapshots.clone();
}
@@ -268,12 +268,12 @@ impl BackfillJobHandle {
}
}
impl std::fmt::Debug for BackfillJobHandle {
impl std::fmt::Debug for crate::BackfillJobHandle {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter
.debug_struct("BackfillJobHandle")
.field("cancellation_requested", &self.is_cancellation_requested())
.field("snapshot", &<BackfillSnapshotSource as ksp_job_api::JobSnapshotSource>::current(&self.snapshots))
.field("snapshot", &<crate::BackfillSnapshotSource as ksp_job_api::JobSnapshotSource>::current(&self.snapshots))
.finish();
}
}
@@ -282,19 +282,19 @@ impl std::fmt::Debug for BackfillJobHandle {
pub struct BackfillJobRuntime {
request: crate::BackfillRequest,
control: BackfillRuntimeControl,
cancellation: BackfillCancellationSignal,
publisher: BackfillRuntimePublisher,
handle: BackfillJobHandle,
cancellation: crate::BackfillCancellationSignal,
publisher: crate::BackfillRuntimePublisher,
handle: crate::BackfillJobHandle,
}
impl BackfillJobRuntime {
impl crate::BackfillJobRuntime {
/// Creates one runtime in `Created` state and its stable latest-value channel.
pub fn new(request: crate::BackfillRequest) -> ksp_core_lib::Result<Self> {
let kind = match ksp_job_api::JobKindCode::new(BACKFILL_JOB_KIND_CODE) {
let kind = match ksp_job_api::JobKindCode::new(crate::BACKFILL_JOB_KIND_CODE) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let initial_snapshot = BackfillJobSnapshot::initial(&request);
let initial_snapshot = crate::BackfillJobSnapshot::initial(&request);
let initial = ksp_job_api::JobNotification::new(
request.job_id().clone(),
kind.clone(),
@@ -305,16 +305,16 @@ impl BackfillJobRuntime {
let (snapshot_sender, snapshot_receiver) = tokio::sync::watch::channel(initial);
let (cancel_sender, cancel_receiver) = tokio::sync::watch::channel(false);
let control = BackfillRuntimeControl::new(cancel_sender);
let cancellation = BackfillCancellationSignal::new(control.token(), cancel_receiver);
let snapshots = BackfillSnapshotSource { receiver: snapshot_receiver };
let handle = BackfillJobHandle { control: control.clone(), snapshots: snapshots.clone() };
let publisher = BackfillRuntimePublisher { sender: snapshot_sender, id: request.job_id().clone(), kind };
let cancellation = crate::BackfillCancellationSignal::new(control.token(), cancel_receiver);
let snapshots = crate::BackfillSnapshotSource { receiver: snapshot_receiver };
let handle = crate::BackfillJobHandle { control: control.clone(), snapshots: snapshots.clone() };
let publisher = crate::BackfillRuntimePublisher { sender: snapshot_sender, id: request.job_id().clone(), kind };
return std::result::Result::Ok(Self { request, control, cancellation, publisher, handle });
}
/// Returns a cloneable control and latest-value observation handle before the runtime is moved into execution.
#[must_use]
pub fn handle(&self) -> BackfillJobHandle {
pub fn handle(&self) -> crate::BackfillJobHandle {
return self.handle.clone();
}
@@ -323,7 +323,7 @@ impl BackfillJobRuntime {
self,
transport: &ksp_onchain_transport_lib::HttpTransportPool,
store: &ksp_store_lib::Store,
) -> ksp_core_lib::Result<BackfillJobSnapshot> {
) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
if self.cancellation.is_requested() {
let claimed = self.control.claim_normal_terminal();
if claimed != TerminalClaim::Cancelled {
@@ -331,7 +331,7 @@ impl BackfillJobRuntime {
}
return self.publisher.publish_cancelled_from_created();
}
let started = self.publisher.publish_running(BackfillJobPhase::Discovering);
let started = self.publisher.publish_running(crate::BackfillJobPhase::Discovering);
if let std::result::Result::Err(error) = started {
self.control.claim_failed();
return std::result::Result::Err(error);
@@ -393,7 +393,7 @@ impl BackfillJobRuntime {
};
}
fn finish_cancelled(&self) -> ksp_core_lib::Result<BackfillJobSnapshot> {
fn finish_cancelled(&self) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
let terminal = self.control.claim_normal_terminal();
if terminal != TerminalClaim::Cancelled {
return std::result::Result::Err(runtime_error("terminal.cancelled"));
@@ -408,7 +408,7 @@ impl BackfillJobRuntime {
/// Internal atomic terminal/cancellation arbitration shared by runtime and external handle.
#[derive(Clone)]
pub(crate) struct BackfillRuntimeControl {
struct BackfillRuntimeControl {
state: std::sync::Arc<std::sync::atomic::AtomicU8>,
token: ksp_job_api::JobCancellationToken,
cancel_sender: tokio::sync::watch::Sender<bool>,
@@ -416,7 +416,7 @@ pub(crate) struct BackfillRuntimeControl {
impl BackfillRuntimeControl {
/// Creates one active control state paired with the cancellation wake channel.
pub(crate) fn new(cancel_sender: tokio::sync::watch::Sender<bool>) -> Self {
fn new(cancel_sender: tokio::sync::watch::Sender<bool>) -> Self {
return Self {
state: std::sync::Arc::new(std::sync::atomic::AtomicU8::new(CONTROL_ACTIVE)),
token: ksp_job_api::JobCancellationToken::new(),
@@ -425,12 +425,12 @@ impl BackfillRuntimeControl {
}
/// Returns the runtime-neutral cancellation token mirrored by this control.
pub(crate) fn token(&self) -> ksp_job_api::JobCancellationToken {
fn token(&self) -> ksp_job_api::JobCancellationToken {
return self.token.clone();
}
/// Atomically accepts the first pre-terminal cancellation request.
pub(crate) fn request_cancellation(&self) -> bool {
fn request_cancellation(&self) -> bool {
let accepted = self
.state
.compare_exchange(CONTROL_ACTIVE, CONTROL_CANCELLATION_REQUESTED, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire)
@@ -442,12 +442,12 @@ impl BackfillRuntimeControl {
}
/// Returns whether cooperative cancellation was accepted.
pub(crate) fn is_cancellation_requested(&self) -> bool {
fn is_cancellation_requested(&self) -> bool {
return self.token.is_cancellation_requested();
}
/// Atomically resolves the completion-versus-cancellation terminal race.
pub(crate) fn claim_normal_terminal(&self) -> TerminalClaim {
fn claim_normal_terminal(&self) -> TerminalClaim {
let completed =
self.state.compare_exchange(CONTROL_ACTIVE, CONTROL_COMPLETED, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire);
if completed.is_ok() {
@@ -466,7 +466,7 @@ impl BackfillRuntimeControl {
}
/// Marks a non-terminal control as failed, overriding a pending cancellation request.
pub(crate) fn claim_failed(&self) {
fn claim_failed(&self) {
loop {
let state = self.state.load(std::sync::atomic::Ordering::Acquire);
if matches!(state, CONTROL_COMPLETED | CONTROL_CANCELLED | CONTROL_FAILED) {
@@ -487,7 +487,7 @@ pub(crate) struct BackfillCancellationSignal {
receiver: tokio::sync::watch::Receiver<bool>,
}
impl BackfillCancellationSignal {
impl crate::BackfillCancellationSignal {
/// Creates one signal from the runtime-neutral token and Tokio wake receiver.
pub(crate) fn new(token: ksp_job_api::JobCancellationToken, receiver: tokio::sync::watch::Receiver<bool>) -> Self {
return Self { token, receiver };
@@ -529,7 +529,7 @@ async fn wait_for_cancellation(token: &ksp_job_api::JobCancellationToken, receiv
/// Internal result of atomically claiming a normal terminal state.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TerminalClaim {
enum TerminalClaim {
Completed,
Cancelled,
Failed,
@@ -538,23 +538,23 @@ pub(crate) enum TerminalClaim {
/// Internal latest-value publisher owning the concrete Backfill notification stream.
#[derive(Clone)]
pub(crate) struct BackfillRuntimePublisher {
sender: tokio::sync::watch::Sender<ksp_job_api::JobNotification<BackfillJobSnapshot>>,
sender: tokio::sync::watch::Sender<ksp_job_api::JobNotification<crate::BackfillJobSnapshot>>,
id: ksp_job_api::JobId,
kind: ksp_job_api::JobKindCode,
}
impl BackfillRuntimePublisher {
impl crate::BackfillRuntimePublisher {
/// Publishes one non-terminal running phase.
pub(crate) fn publish_running(&self, phase: BackfillJobPhase) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_running(&self, phase: crate::BackfillJobPhase) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Running, |snapshot| {
snapshot.phase = phase;
});
}
/// Publishes the complete bounded discovery result as the current execution snapshot.
pub(crate) fn publish_discovery(&self, discovery: &crate::BackfillDiscovery) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_discovery(&self, discovery: &crate::BackfillDiscovery) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Running, |snapshot| {
snapshot.phase = BackfillJobPhase::Executing;
snapshot.phase = crate::BackfillJobPhase::Executing;
snapshot.discovery_boundary = std::option::Option::Some(discovery.boundary());
snapshot.candidates_selected = discovery.candidates().len();
});
@@ -566,25 +566,25 @@ impl BackfillRuntimePublisher {
progress: &crate::BackfillExecutionProgress,
cancelling: bool,
draining: bool,
) -> ksp_core_lib::Result<BackfillJobSnapshot> {
) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
let state = if cancelling { ksp_job_api::JobState::Cancelling } else { ksp_job_api::JobState::Running };
return self.publish_with(state, |snapshot| {
snapshot.phase = if draining { BackfillJobPhase::Draining } else { BackfillJobPhase::Executing };
snapshot.phase = if draining { crate::BackfillJobPhase::Draining } else { crate::BackfillJobPhase::Executing };
apply_progress(snapshot, progress);
});
}
/// Publishes cancellation observation before terminal cancellation.
pub(crate) fn publish_cancelling(&self) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_cancelling(&self) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Cancelling, |snapshot| {
snapshot.phase = BackfillJobPhase::Draining;
snapshot.phase = crate::BackfillJobPhase::Draining;
});
}
/// Publishes the drained batch state while cancellation is terminalizing.
pub(crate) fn publish_batch_cancelling(&self, batch: &crate::BackfillExecutionBatch) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_batch_cancelling(&self, batch: &crate::BackfillExecutionBatch) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Cancelling, |snapshot| {
snapshot.phase = BackfillJobPhase::Draining;
snapshot.phase = crate::BackfillJobPhase::Draining;
apply_batch(snapshot, batch);
});
}
@@ -595,16 +595,16 @@ impl BackfillRuntimePublisher {
batch: &crate::BackfillExecutionBatch,
state: ksp_job_api::JobState,
failure_code: std::option::Option<ksp_core_lib::ErrorCode>,
) -> ksp_core_lib::Result<BackfillJobSnapshot> {
) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(state, |snapshot| {
snapshot.phase = BackfillJobPhase::Finished;
snapshot.phase = crate::BackfillJobPhase::Finished;
snapshot.failure_code = failure_code;
apply_batch(snapshot, batch);
});
}
/// Publishes a terminal failure before a batch exists.
pub(crate) fn publish_failed(&self, code: ksp_core_lib::ErrorCode) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_failed(&self, code: ksp_core_lib::ErrorCode) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_terminal(ksp_job_api::JobState::Failed, std::option::Option::Some(code));
}
@@ -613,23 +613,23 @@ impl BackfillRuntimePublisher {
&self,
state: ksp_job_api::JobState,
failure_code: std::option::Option<ksp_core_lib::ErrorCode>,
) -> ksp_core_lib::Result<BackfillJobSnapshot> {
) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(state, |snapshot| {
snapshot.phase = BackfillJobPhase::Finished;
snapshot.phase = crate::BackfillJobPhase::Finished;
snapshot.failure_code = failure_code;
});
}
/// Publishes direct Created-to-Cancelled termination before execution starts.
pub(crate) fn publish_cancelled_from_created(&self) -> ksp_core_lib::Result<BackfillJobSnapshot> {
pub(crate) fn publish_cancelled_from_created(&self) -> ksp_core_lib::Result<crate::BackfillJobSnapshot> {
return self.publish_with(ksp_job_api::JobState::Cancelled, |snapshot| {
snapshot.phase = BackfillJobPhase::Finished;
snapshot.phase = crate::BackfillJobPhase::Finished;
});
}
fn publish_with<F>(&self, state: ksp_job_api::JobState, update: F) -> ksp_core_lib::Result<BackfillJobSnapshot>
fn publish_with<F>(&self, state: ksp_job_api::JobState, update: F) -> ksp_core_lib::Result<crate::BackfillJobSnapshot>
where
F: FnOnce(&mut BackfillJobSnapshot),
F: FnOnce(&mut crate::BackfillJobSnapshot),
{
let current = self.sender.borrow().clone();
if current.state().is_terminal() {
@@ -668,7 +668,7 @@ fn valid_snapshot_transition(source: ksp_job_api::JobState, target: ksp_job_api:
);
}
fn apply_progress(snapshot: &mut BackfillJobSnapshot, progress: &crate::BackfillExecutionProgress) {
fn apply_progress(snapshot: &mut crate::BackfillJobSnapshot, progress: &crate::BackfillExecutionProgress) {
snapshot.candidates_admitted = progress.admitted_count();
snapshot.candidates_finished = progress.finished_count();
snapshot.entities_inserted = progress.inserted_count();
@@ -685,7 +685,7 @@ fn apply_progress(snapshot: &mut BackfillJobSnapshot, progress: &crate::Backfill
snapshot.checkpoint = std::option::Option::Some(progress.checkpoint().clone());
}
fn apply_batch(snapshot: &mut BackfillJobSnapshot, batch: &crate::BackfillExecutionBatch) {
fn apply_batch(snapshot: &mut crate::BackfillJobSnapshot, batch: &crate::BackfillExecutionBatch) {
snapshot.candidates_admitted = batch.admitted_count();
snapshot.candidates_finished = batch.finished_count();
snapshot.entities_inserted = batch.inserted_count();

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/tests/public_api.rs
// version: 6
// version: 7
//! Public API canaries through the concrete Backfill cancellation and latest-value runtime tranche.
@@ -109,13 +109,13 @@ fn pre_008_checkpoint_and_bounded_execution_contracts_are_available_from_crate_r
#[test]
fn pre_009_concrete_runtime_snapshot_and_control_contracts_are_available_from_crate_root() {
fn assert_source<T>()
fn assert_source<T>(_: std::marker::PhantomData<T>)
where
T: ksp_job_api::JobSnapshotSource<Snapshot = ksp_job_backfill_lib::BackfillJobSnapshot>,
{
return;
}
assert_source::<ksp_job_backfill_lib::BackfillSnapshotSource>();
assert_source(std::marker::PhantomData::<ksp_job_backfill_lib::BackfillSnapshotSource>);
let _runtime_new = ksp_job_backfill_lib::BackfillJobRuntime::new;
let _handle: std::option::Option<ksp_job_backfill_lib::BackfillJobHandle> = std::option::Option::None;
let _phase = ksp_job_backfill_lib::BackfillJobPhase::Discovering;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/unit_tests/discovery.rs
// version: 5
// version: 6
#[derive(Clone, Debug, Eq, PartialEq)]
struct PageCall {
@@ -392,13 +392,14 @@ async fn pre_009_discovery_rpc_wait_is_cancelled_cooperatively() {
std::option::Option::None => return,
};
let source = PendingSource::new();
let token = ksp_job_api::JobCancellationToken::new();
let (cancel_sender, cancel_receiver) = tokio::sync::watch::channel(false);
let control = crate::BackfillRuntimeControl::new(cancel_sender);
let cancellation = crate::BackfillCancellationSignal::new(control.token(), cancel_receiver);
let cancellation = crate::BackfillCancellationSignal::new(token.clone(), cancel_receiver);
let discovery = super::discover_with_source(&source, &request, std::option::Option::Some(&cancellation));
let cancel = async {
tokio::task::yield_now().await;
assert!(control.request_cancellation());
assert!(token.cancel());
assert!(cancel_sender.send(true).is_ok());
};
let (result, ()) = tokio::join!(discovery, cancel);
let error = match result {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/unit_tests/execution.rs
// version: 2
// version: 3
#[derive(Clone, Copy)]
enum FakeDisposition {
@@ -291,9 +291,9 @@ async fn pre_009_cancellation_stops_admission_and_drains_already_admitted_candid
FakePlan { pending_polls: 0, disposition: FakeDisposition::Durable },
FakePlan { pending_polls: 0, disposition: FakeDisposition::Durable },
]);
let token = ksp_job_api::JobCancellationToken::new();
let (cancel_sender, cancel_receiver) = tokio::sync::watch::channel(false);
let control = crate::runtime::BackfillRuntimeControl::new(cancel_sender);
let signal = crate::runtime::BackfillCancellationSignal::new(control.token(), cancel_receiver);
let signal = crate::BackfillCancellationSignal::new(token.clone(), cancel_receiver);
let execution = super::execute_with_processor(&processor, &request, &discovery, std::option::Option::Some(&signal), std::option::Option::None);
let cancellation = async {
loop {
@@ -302,7 +302,8 @@ async fn pre_009_cancellation_stops_admission_and_drains_already_admitted_candid
}
tokio::task::yield_now().await;
}
assert!(control.request_cancellation());
assert!(token.cancel());
assert!(cancel_sender.send(true).is_ok());
};
let (result, ()) = tokio::join!(execution, cancellation);
let batch = match result {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-job-backfill-lib/unit_tests/runtime.rs
// version: 2
// version: 3
use ksp_job_api::JobSnapshotSource; // rust-rules: trait-import
@@ -78,7 +78,7 @@ async fn pre_009_terminal_snapshot_is_retained_and_late_cancellation_is_rejected
let listener = handle.snapshots();
let started = runtime.publisher.publish_running(crate::BackfillJobPhase::Discovering);
assert!(started.is_ok());
assert_eq!(runtime.control.claim_normal_terminal(), crate::TerminalClaim::Completed);
assert_eq!(runtime.control.claim_normal_terminal(), super::TerminalClaim::Completed);
let terminal = runtime.publisher.publish_terminal(ksp_job_api::JobState::Completed(ksp_job_api::JobCompletion::Complete), std::option::Option::None);
assert!(terminal.is_ok());
assert!(!handle.cancel());
@@ -94,13 +94,13 @@ async fn pre_009_terminal_snapshot_is_retained_and_late_cancellation_is_rejected
#[test]
fn pre_009_terminal_race_is_first_decision_wins_for_cancellation_vs_completion() {
let (cancel_sender, _) = tokio::sync::watch::channel(false);
let cancellation_first = crate::BackfillRuntimeControl::new(cancel_sender);
let cancellation_first = super::BackfillRuntimeControl::new(cancel_sender);
assert!(cancellation_first.request_cancellation());
assert_eq!(cancellation_first.claim_normal_terminal(), crate::TerminalClaim::Cancelled);
assert_eq!(cancellation_first.claim_normal_terminal(), super::TerminalClaim::Cancelled);
assert!(!cancellation_first.request_cancellation());
let (cancel_sender, _) = tokio::sync::watch::channel(false);
let completion_first = crate::BackfillRuntimeControl::new(cancel_sender);
assert_eq!(completion_first.claim_normal_terminal(), crate::TerminalClaim::Completed);
let completion_first = super::BackfillRuntimeControl::new(cancel_sender);
assert_eq!(completion_first.claim_normal_terminal(), super::TerminalClaim::Completed);
assert!(!completion_first.request_cancellation());
return;
}
@@ -108,10 +108,10 @@ fn pre_009_terminal_race_is_first_decision_wins_for_cancellation_vs_completion()
#[test]
fn pre_009_fatal_failure_overrides_pending_cancellation_before_terminal_publication() {
let (cancel_sender, _) = tokio::sync::watch::channel(false);
let control = crate::BackfillRuntimeControl::new(cancel_sender);
let control = super::BackfillRuntimeControl::new(cancel_sender);
assert!(control.request_cancellation());
control.claim_failed();
assert_eq!(control.claim_normal_terminal(), crate::TerminalClaim::Failed);
assert_eq!(control.claim_normal_terminal(), super::TerminalClaim::Failed);
assert!(!control.request_cancellation());
return;
}
@@ -119,7 +119,7 @@ fn pre_009_fatal_failure_overrides_pending_cancellation_before_terminal_publicat
#[tokio::test]
async fn pre_009_long_running_pre_store_future_is_cancelled_cooperatively() {
let (cancel_sender, cancel_receiver) = tokio::sync::watch::channel(false);
let control = crate::BackfillRuntimeControl::new(cancel_sender);
let control = super::BackfillRuntimeControl::new(cancel_sender);
let signal = crate::BackfillCancellationSignal::new(control.token(), cancel_receiver);
let operation = std::future::pending::<ksp_core_lib::Result<usize>>();
let wait = signal.run_cancellable(operation);

View File

@@ -0,0 +1,124 @@
<!-- file: deltas/0.3.6/pre.009-fix.001.md -->
<!-- version: 1 -->
# Delta v0.3.6-pre.009-fix.001
## Base requise
- `0.3.6-pre.9` / livraison `pre.009`.
- Le gate opérateur de `pre.009` confirme `cargo check --workspace` et l'exécution intégrale des 47 tests unitaires + 10 canaries d'intégration.
- Clippy reste non warning-free avec deux réexports `pub(crate)` inutilisés (`BackfillRuntimeControl`, `TerminalClaim`) et une canarie `public_api` dont le paramètre générique n'est pas effectivement utilisé.
- L'inspection de la cause révèle un défaut plus fondamental : plusieurs items `pub`/`pub(crate)` partagés de `ksp-job-backfill-lib` sont encore référencés par nom local depuis leur module propriétaire, contrairement à `RUST-IMPORT-009`; deux helpers runtime n'ont en outre aucune justification de visibilité crate-wide selon `RUST-API-007`.
## Correctif de visibilité et de chemins crate-root
Le correctif ne masque pas les warnings. Il réconcilie toute la crate `ksp-job-backfill-lib` avec les contrats Rust applicables :
- tout item partagé `pub` ou `pub(crate)` réexporté à la racine est référencé via `crate::Item`, y compris depuis son module de déclaration ;
- les implémentations des types partagés utilisent la façade crate-root (`impl crate::Type`, `impl Trait for crate::Type`) ;
- aucun chemin `crate::module::Item` n'est conservé pour une surface partagée ;
- les unit tests attachés à un module continuent d'utiliser `crate::Item` pour les items partagés et `super::Item` pour les items strictement privés.
Cette réconciliation couvre les surfaces déjà présentes de la crate (`BackfillCheckpoint`, discovery, conversion, persistence, bounded execution) afin de ne pas laisser dans le même composant des violations identiques à celles révélées par `pre.009`.
## Visibilité runtime
`BackfillRuntimeControl` et `TerminalClaim` sont utilisés uniquement par `runtime.rs` et ses unit tests attachés. Ils redeviennent donc privés :
```text
BackfillRuntimeControl pub(crate) -> private
TerminalClaim pub(crate) -> private
```
Leurs réexports crate-root sont supprimés. Les unit tests de `runtime.rs` les consomment via `super::BackfillRuntimeControl` et `super::TerminalClaim`, conformément à `RUST-IMPORT-012` et `RUST-API-007`.
Les canaries discovery/execution n'élargissent plus artificiellement cette visibilité pour provoquer une annulation. Elles construisent directement :
```text
JobCancellationToken
watch<bool>
crate::BackfillCancellationSignal
```
puis déclenchent à la fois le token runtime-neutral et le réveil `watch` privé.
`BackfillCancellationSignal` et `BackfillRuntimePublisher` restent `pub(crate)` car ils sont réellement consommés par d'autres modules de production ; leurs implémentations et usages passent par `crate::...`.
## Canary Clippy publique
La canarie `pre_009_concrete_runtime_snapshot_and_control_contracts_are_available_from_crate_root` conserve son assertion générique sur `JobSnapshotSource`, mais le type générique est désormais porté par un argument `PhantomData<T>`. Le paramètre n'est donc plus considéré comme inutilisé par `clippy::extra_unused_type_parameters`.
Aucun contrat public, état Job, sémantique d'annulation, comportement latest-value, frontier/checkpoint, dépendance ou feature ne change.
## Cargo et versions de fichiers
- `workspace.package.version` : `0.3.6-pre.9` -> `0.3.6-pre.9.fix.1`.
- `Cargo.toml` : version d'en-tête `408` -> `409`.
- `src/checkpoint.rs` : `1` -> `2`.
- `src/conversion.rs` : `3` -> `4`.
- `src/discovery.rs` : `4` -> `5`.
- `src/execution.rs` : `2` -> `3`.
- `src/lib.rs` : `5` -> `6`.
- `src/persistence.rs` : `2` -> `3`.
- `src/request.rs` : `3` -> `4`.
- `src/runtime.rs` : `2` -> `3`.
- `tests/public_api.rs` : `6` -> `7`.
- `unit_tests/discovery.rs` : `5` -> `6`.
- `unit_tests/execution.rs` : `2` -> `3`.
- `unit_tests/runtime.rs` : `2` -> `3`.
- plan 027 : `16` -> `17`.
- validation 023 : `16` -> `17`.
## Graphe de dépendances
Aucune dépendance ni feature ne change dans ce fix. Les arbres Cargo ont déjà été inspectés au gate de `pre.009`, puisque cette tranche promouvait Tokio en dépendance normale privée. Ils ne sont donc pas à rejouer pour `pre.009-fix.001` sauf si l'opérateur effectue séparément un `cargo clean` et souhaite revalider le graphe après reconstruction propre.
## Payload
Ajout :
- `deltas/0.3.6/pre.009-fix.001.md`
Modifications :
- `Cargo.toml`
- `crates/ksp-job-backfill-lib/src/checkpoint.rs`
- `crates/ksp-job-backfill-lib/src/conversion.rs`
- `crates/ksp-job-backfill-lib/src/discovery.rs`
- `crates/ksp-job-backfill-lib/src/execution.rs`
- `crates/ksp-job-backfill-lib/src/lib.rs`
- `crates/ksp-job-backfill-lib/src/persistence.rs`
- `crates/ksp-job-backfill-lib/src/request.rs`
- `crates/ksp-job-backfill-lib/src/runtime.rs`
- `crates/ksp-job-backfill-lib/tests/public_api.rs`
- `crates/ksp-job-backfill-lib/unit_tests/discovery.rs`
- `crates/ksp-job-backfill-lib/unit_tests/execution.rs`
- `crates/ksp-job-backfill-lib/unit_tests/runtime.rs`
- `docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md`
- `docs/validation/023-V0_3_6_JOB_API_BACKFILL.md`
Aucune suppression.
## Validations exécutées à l'assemblage
```text
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.6
```
Un contrôle ciblé supplémentaire vérifie dans `ksp-job-backfill-lib` qu'aucun item réexporté au crate-root n'est encore référencé localement par son nom nu dans son module propriétaire et qu'aucun `crate::module::Item` ne subsiste pour les surfaces partagées.
L'environnement d'assemblage ne fournit ni Cargo, ni Rustc, ni Rustfmt ; le gate Rust du fix reste donc à exécuter par l'opérateur.
## Gate opérateur demandé
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.6
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-job-backfill-lib
```
Attendu : 47 tests unitaires et 10 canaries d'intégration, sans warning Clippy. Aucun `cargo tree` n'est demandé pour ce fix sans changement de dépendances/features.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md -->
<!-- version: 16 -->
<!-- version: 17 -->
# Plan v0.3.6 — Job API et premier backfill RAW
@@ -492,19 +492,25 @@ La reprise suit les quatre sémantiques figées : Latest repart de la vue couran
#### `pre.008-fix.001` — Masquage du helper `request` dans la canarie checkpoint
**Statut : matérialisé ; gate opérateur à rejouer.**
**Statut : clôturé ; gate opérateur vert.**
Le gate opérateur de `pre.008` confirme `cargo fmt`, les audits Rust/Markdown et `cargo check --workspace`, puis la compilation des targets de test échoue en `E0618`/`E0282` : dans `pre_008_checkpoint_is_bound_to_job_and_scope_fingerprint`, la variable locale `request` masque le helper homonyme avant la construction du second scope. Le fix renomme uniquement cette liaison locale en `primary_request` et synchronise ses usages ; aucun contrat, test, comportement runtime, checkpoint, dépendance ou feature ne change. Comme un fichier Rust est corrigé, la version workspace devient `0.3.6-pre.8.fix.1`. Le gate réduit du fix est ensuite intégralement vert : audits, `cargo check`, Clippy sans warning, 39 unitaires et 9 canaries d'intégration passent. Les arbres Cargo de `pre.008` avaient déjà été inspectés puisque cette tranche ajoutait `futures-util`; ils n'avaient pas à être rejoués pour ce fix sans changement de graphe.
### `pre.009` — Annulation et snapshots concrets
**Statut : matérialisé ; gate opérateur à rejouer.**
**Statut : matérialisé ; gate fonctionnel vert mais qualité Rust bloquée, corrigée par `pre.009-fix.001`.**
Budget cible : **15-20 min**. Entrée : frontière prouvée et gate `pre.008-fix.001` vert. La tranche matérialise `BackfillJobRuntime`, `BackfillJobHandle`, `BackfillJobSnapshot` et `BackfillSnapshotSource` comme runtime concret du vertical RAW transaction. `ksp-job-api` reste inchangée, passive et runtime-neutral ; Tokio devient uniquement un détail d'implémentation normal de `ksp-job-backfill-lib` (`macros` + `sync`) et aucun type Tokio n'est exposé dans la racine publique. Le canal concret est un `watch` latest-value O(1) : les progressions intermédiaires peuvent être coalescées, chaque listener possède son receiver cloné, et la dernière valeur terminale reste lisible tant que le handle/source existe.
L'annulation réutilise `JobCancellationToken` et ajoute un signal réveillable privé. Les attentes de découverte et d'hydratation pré-Store sont abandonnables ; une vérification supplémentaire intervient après hydratation et avant soumission Store. Dès que `persist_backfill_hydration` est appelée, l'écriture atomique n'est plus sélectionnée contre l'annulation : toute opération Store déjà soumise est drainée jusqu'à un résultat connu. L'exécuteur arrête ensuite les nouvelles admissions, draine l'in-flight et publie frontier/checkpoint sûrs. Une arbitration atomique sépare `ACTIVE`, demande d'annulation et états terminaux : une annulation acceptée avant la décision terminale gagne sur une complétion normale, une complétion déjà réclamée refuse l'annulation tardive, et une erreur fatale peut remplacer une annulation pendante afin de ne pas masquer un échec Transport/Store.
Le snapshot complet reste sûr : phase, catégorie de scope, borne de découverte, compteurs d'admission/fin, dispositions entité/observation, missing/conflits/annulations/trous, maximum in-flight, préfixe contigu, checkpoint et éventuel code d'erreur stable ; aucune URL, endpoint/provider, payload RAW ou message fournisseur n'y entre. La tranche matérialise 47 tests unitaires et 10 canaries d'intégration au total, dont listener lent/multi-listeners, rétention terminale, courses terminales, attente pré-Store longue, attente de page RPC pendante et drainage après annulation. Leur exécution Rust reste à confirmer par le gate opérateur. `pre.010` reste fermé au hardening et aux canaries externes.
Le snapshot complet reste sûr : phase, catégorie de scope, borne de découverte, compteurs d'admission/fin, dispositions entité/observation, missing/conflits/annulations/trous, maximum in-flight, préfixe contigu, checkpoint et éventuel code d'erreur stable ; aucune URL, endpoint/provider, payload RAW ou message fournisseur n'y entre. La tranche matérialise 47 tests unitaires et 10 canaries d'intégration au total, dont listener lent/multi-listeners, rétention terminale, courses terminales, attente pré-Store longue, attente de page RPC pendante et drainage après annulation. Le gate opérateur exécute ces 47 + 10 tests avec succès, mais Clippy révèle deux réexports `pub(crate)` non consommés via la façade crate-root et une canarie générique à type non utilisé ; l'audit manuel montre en outre que plusieurs usages intra-module des items partagés `pub`/`pub(crate)` de la nouvelle crate contournent `crate::`, en contradiction avec `RUST-IMPORT-009`, `RUST-IMPORT-012` et `RUST-API-007`. `pre.010` reste fermé jusqu'au gate vert du fix.
#### `pre.009-fix.001` — Normalisation crate-root des items partagés et visibilité runtime
**Statut : matérialisé ; gate opérateur du fix à rejouer.**
Le fix réconcilie l'intégralité de `ksp-job-backfill-lib` avec les règles de visibilité/import applicables. Tout item public ou crate-wide réexporté au crate-root est désormais référencé via `crate::Item`, y compris depuis son module propriétaire. `BackfillRuntimeControl` et `TerminalClaim`, utilisés uniquement par `runtime.rs` et ses unit tests attachés, redeviennent strictement privés conformément à `RUST-API-007`; les unit tests du module runtime y accèdent via `super::Item`. Les tests discovery/execution ne dépendent plus de cette visibilité artificielle et déclenchent l'annulation avec `JobCancellationToken` + canal `watch` directement. La canarie publique utilise désormais son paramètre générique via `PhantomData`, supprimant `clippy::extra_unused_type_parameters`. Aucun contrat externe, comportement runtime, dépendance ou feature ne change.
### `pre.010` — Hardening et canaries externes

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/023-V0_3_6_JOB_API_BACKFILL.md -->
<!-- version: 16 -->
<!-- version: 17 -->
# Validation v0.3.6 — Job API et premier backfill RAW
@@ -189,7 +189,7 @@ Aucune entrée absolue, traversée, avec séparateur inversé ou lien symbolique
## 13. Annulation et résultats terminaux
`pre.009` matérialise le runtime concret, le canal latest-value et les chemins d'annulation/drainage. Les canaries couvrent l'attente RPC de découverte, une future pré-Store longue, le drainage des candidats déjà admis, les listeners lents/indépendants, la rétention terminale et les arbitrations terminales ; le gate Rust opérateur reste à rejouer avant de cocher ces critères comportementaux.
`pre.009` matérialise le runtime concret, le canal latest-value et les chemins d'annulation/drainage. Le gate opérateur exécute avec succès les 47 unitaires et 10 canaries couvrant l'attente RPC de découverte, une future pré-Store longue, le drainage des candidats déjà admis, les listeners lents/indépendants, la rétention terminale et les arbitrations terminales. La qualité Rust reste néanmoins bloquée jusqu'au gate de `pre.009-fix.001`, qui corrige les chemins crate-root/visibilités et le warning Clippy de la canarie publique.
- [ ] Annulation idempotente et état `Cancelling` observable.
- [ ] Admissions arrêtées après observation du token.
@@ -209,7 +209,8 @@ Aucune entrée absolue, traversée, avec séparateur inversé ou lien symbolique
- [X] Versions/features auditées : aucune nouvelle version externe ; `sha2`, `futures-util` et `tokio` réutilisent les entrées workspace existantes. `futures-util` reste un détail normal privé depuis `pre.008` et `pre.009` promeut Tokio en dépendance normale privée limitée à `macros` + `sync`; aucun type Tokio n'est exposé.
- [X] `pre.005` matérialise 11 tests unitaires et 6 canaries dintégration ; le gate opérateur les exécute tous avec succès.
- [X] `pre.006` matérialise 19 tests unitaires et 7 canaries dintégration ; le gate opérateur les exécute tous avec succès, avec uniquement le warning Clippy corrigé par `pre.006-fix.001`.
- [X] `cargo clippy --workspace --all-targets` warning-free sur `pre.008-fix.001` avec 39 unitaires et 9 canaries vertes ; le gate `pre.009` reste à rejouer.
- [X] `pre.009` : `cargo check --workspace` vert et 47 unitaires + 10 canaries vertes ; Clippy signale 3 warnings de normalisation/API de test, corrigés par `pre.009-fix.001` mais encore à revalider.
- [X] Audit manuel `pre.009-fix.001` : les items partagés de `ksp-job-backfill-lib` passent par `crate::Item`; `BackfillRuntimeControl`/`TerminalClaim` restent privés et les unit tests runtime utilisent `super::Item`, conformément à RUST-IMPORT-009/012 et RUST-API-007.
- [ ] `cargo fmt --all -- --check` vert.
- [X] `scripts/audit_rust_workspace_rules.py` vert sur `pre.005` dans l'environnement d'assemblage et dans le gate opérateur.
- [X] `scripts/audit_markdown_tables.py` vert sur `pre.005` dans le gate opérateur (264 tables / 147 fichiers, delta inclus).