feat(miroir-core): add reshard executor source document-count helper (bf-14wgq)

Add `compute_source_document_count` on `ReshardExecutor` — the reshard
backfill denominator (bf-2ynu5, AC #1). It delegates to the shared
`crate::index_stats::aggregate_index_stats`, reusing the executor's
existing `http_client` field as the reqwest transport, and returns
`total_documents` (reduced via `max` across source-node replicas).

Pure additive: no change to `start_backfill`, `advance_backfill`, or
persisted `ReshardOperation`. Wiring into `start_backfill` is a sibling
bead. `cargo build -p miroir-core` green.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-07-09 09:01:26 -04:00
parent 3b52aba39a
commit 3dd2b75e2b

View file

@ -529,6 +529,36 @@ impl<C: NodeClient> ReshardExecutor<C> {
Ok(())
}
/// Aggregate the source index's real document count across all source nodes.
///
/// This is the reshard backfill denominator described in plan §13.1 step 3
/// (bf-2ynu5). It delegates to the shared [`crate::index_stats`] module so
/// that reshard and ILM rollover (plan §13.17) compute an index's document
/// count the *same* way: iterate the source `node_addresses`,
/// `GET /indexes/{uid}/stats`, and reduce `numberOfDocuments` with `max`
/// (each address hosts a full replica, so `sum` would over-count by the
/// replication factor).
///
/// The aggregation is infallible from the caller's perspective — a node that
/// fails to respond (network / non-2xx / parse error) is logged and skipped,
/// and the count is `0` only if every node fails or reports zero — but this
/// returns [`Result`] to match the executor's method conventions and leave
/// the `start_backfill` caller (wired in a sibling bead) free to propagate a
/// future failure mode.
///
/// Pure additive helper: this method is *not* called from the reshard state
/// machine in this bead.
pub async fn compute_source_document_count(&self, index_uid: &str) -> Result<u64> {
let stats = crate::index_stats::aggregate_index_stats(
&self.http_client,
&self.node_addresses,
&self.master_key,
index_uid,
)
.await;
Ok(stats.total_documents)
}
/// Check if backfill is complete.
async fn is_backfill_complete(&self, state: &ReshardState) -> Result<bool> {
Ok(state