| @@ -258,9 +258,9 @@ | ||
| 258 | 258 | ); |
| 259 | 259 | } |
| 260 | 260 | |
| 261 | 261 | if ( null !== $bucket_raw && '' !== $bucket_raw ) { |
| 262 | - if ( 'products' !== $collection ) { | |
| 262 | + if ( null === ( Collections::row( $collection )['repair']['drill_down'] ?? null ) ) { | |
| 263 | 263 | return new WP_Error( |
| 264 | 264 | 'woocommerce_pos_sync_unsupported_scan_drill_down_collection', |
| 265 | 265 | \sprintf( 'integrity/scan drill-down does not support the "%s" collection', $collection ), |
| 266 | 266 | array( 'status' => 400 ) |
| @@ -266,9 +266,9 @@ | ||
| 266 | 266 | array( 'status' => 400 ) |
| 267 | 267 | ); |
| 268 | 268 | } |
| 269 | 269 | $bucket = max( 0, (int) $bucket_raw ); |
| 270 | - if ( $this->maybe_schedule_digest_rebuild() ) { | |
| 270 | + if ( true === Collections::row( $collection )['repair']['self_heal'] && $this->maybe_schedule_digest_rebuild() ) { | |
| 271 | 271 | return rest_ensure_response( |
| 272 | 272 | $this->envelope( |
| 273 | 273 | array( |
| 274 | 274 | 'bucket_size' => $bucket_size, |
| @@ -296,9 +296,9 @@ | ||
| 296 | 296 | $first_bucket = $after_id > 0 ? ( (int) floor( $after_id / $bucket_size ) ) + 1 : 0; |
| 297 | 297 | $window_start = $first_bucket * $bucket_size; |
| 298 | 298 | $window_end = ( $first_bucket + $limit_buckets ) * $bucket_size; |
| 299 | 299 | |
| 300 | - if ( 'products' === $collection && $this->maybe_schedule_digest_rebuild() ) { | |
| 300 | + if ( true === Collections::row( $collection )['repair']['self_heal'] && $this->maybe_schedule_digest_rebuild() ) { | |
| 301 | 301 | return rest_ensure_response( |
| 302 | 302 | $this->envelope( |
| 303 | 303 | array( |
| 304 | 304 | 'bucket_size' => $bucket_size, |
| @@ -442,9 +442,10 @@ | ||
| 442 | 442 | * refreshes its own copy, but nothing rewrites the STORED digest, so the |
| 443 | 443 | * bucket mismatches forever and the till shows a permanent "records need |
| 444 | 444 | * attention" for data that is already correct. Observed on dev-pro |
| 445 | 445 | * 2026-08-19: 138 products drifted by a hookless bulk edit, re-escalated |
| 446 | - * every sweep, local copies byte-identical to the server. | |
| 446 | + * every sweep, local copies byte-identical to the server. Missing stored | |
| 447 | + * digests in a partially populated table likewise need rebuilding. | |
| 447 | 448 | * |
| 448 | 449 | * Why a STREAK and not the first sight of drift: for a hookless write the |
| 449 | 450 | * integrity scan is the ONLY signal — such a write bypasses the sequence |
| 450 | 451 | * log too — so re-baselining on first detection would erase the one thing |
| @@ -461,9 +462,9 @@ | ||
| 461 | 462 | */ |
| 462 | 463 | private function maybe_schedule_stale_digest_rebuild( int $bucket, int $bucket_size, array $changes ): void { |
| 463 | 464 | $stale = 0; |
| 464 | 465 | foreach ( $changes as $change ) { |
| 465 | - if ( 'changed' === ( $change['status'] ?? '' ) ) { | |
| 466 | + if ( \in_array( $change['status'] ?? '', array( 'changed', 'missing_stored' ), true ) ) { | |
| 466 | 467 | ++$stale; |
| 467 | 468 | } |
| 468 | 469 | } |
| 469 | 470 | |
| @@ -474,9 +475,9 @@ | ||
| 474 | 475 | |
| 475 | 476 | $streak_key = $bucket_size . ':' . $bucket; |
| 476 | 477 | |
| 477 | 478 | if ( 0 === $stale ) { |
| 478 | - // Reconciled (or only deletions/missing_stored left) — forget it. | |
| 479 | + // Reconciled (or only deletions left) — forget it. | |
| 479 | 480 | if ( isset( $streaks[ $streak_key ] ) ) { |
| 480 | 481 | unset( $streaks[ $streak_key ] ); |
| 481 | 482 | $this->save_drift_streaks( $streaks ); |
| 482 | 483 | } |