From 6c782282c70039438249b981034a198a0c9340f9 Mon Sep 17 00:00:00 2001 From: wpdev Date: Sun, 27 Sep 2026 00:28:00 +0800 Subject: [PATCH] feat(2meet-data-optimizer): sample drift daily and record the result - TMDO_Drift_Check::cron_tick() runs on the existing wpdo_daily_health_check event (no new schedule to add or clear): every entity type whose flat tables are written (dual_write or higher) is checked on 500 random entities per group, and the outcome replaces the recorded status, so a group found drifting leaves the router that day. - Verify: DriftCheckTest cron cases (RED: method / hook missing); dev36 with all four entities in dual_write: one tick over 22 groups in 10 s, 0 drift, status option restored. - PR: PR-073 --- includes/class-tmdo-core.php | 3 ++ .../diagnostic/class-tmdo-drift-check.php | 23 +++++++++++ tests/integration/DriftCheckTest.php | 39 +++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/includes/class-tmdo-core.php b/includes/class-tmdo-core.php index e661487..4c28256 100644 --- a/includes/class-tmdo-core.php +++ b/includes/class-tmdo-core.php @@ -296,6 +296,9 @@ class TMDO_Core { // v2.3.0 M6: daily health probe cron. add_action( 'wpdo_daily_health_check', array( 'TMDO_Health_Cron', 'run' ) ); + // 1.2.0: daily flat-vs-native drift sample (read by the post query router). + add_action( 'wpdo_daily_health_check', array( 'TMDO_Drift_Check', 'cron_tick' ) ); + // v2.3.0 M6: daily snapshot prune cron (paired with health check). add_action( 'wpdo_snapshot_prune_daily', array( 'TMDO_Snapshot_Pruner', 'cron_run' ) ); diff --git a/includes/diagnostic/class-tmdo-drift-check.php b/includes/diagnostic/class-tmdo-drift-check.php index d0dc7ff..35bad01 100644 --- a/includes/diagnostic/class-tmdo-drift-check.php +++ b/includes/diagnostic/class-tmdo-drift-check.php @@ -40,6 +40,11 @@ final class TMDO_Drift_Check { */ public const OPTION = 'wpdo_drift_status'; + /** + * Entities per group sampled by the daily check. + */ + public const CRON_SAMPLE = 500; + /** * MySQL regex for a plain decimal number, used to skip rows that obviously match. * Exact matches compare BINARY: meta and flat tables may differ in collation, @@ -97,6 +102,24 @@ final class TMDO_Drift_Check { return $report; } + /** + * Daily check (wpdo_daily_health_check): sample every entity type whose + * flat tables are written (mode dual_write or higher). The outcome replaces + * the recorded status, so a group found drifting leaves the router at once. + */ + public static function cron_tick(): void { + foreach ( TMDO_Entity_Registry::VALID_ENTITY_TYPES as $entity_type ) { + if ( ! TMDO_Mode_Manager::writes_to_flat( $entity_type ) ) { + continue; + } + try { + self::run( $entity_type, '', self::CRON_SAMPLE ); + } catch ( InvalidArgumentException $e ) { + continue; // No adapter for this entity type on this site. + } + } + } + /** * Rebuild drifted flat rows from native meta, then check again. * diff --git a/tests/integration/DriftCheckTest.php b/tests/integration/DriftCheckTest.php index f16af35..76a0ff5 100644 --- a/tests/integration/DriftCheckTest.php +++ b/tests/integration/DriftCheckTest.php @@ -181,6 +181,45 @@ class DriftCheckTest extends TestCase { $this->assertFalse( TMDO_Cache_Orchestrator::get_row( 'user', 2, 'dc_grp' ) ); } + // ── cron_tick() (2.2 PR-073) ──────────────────────────────────────────── + + private static function set_modes( ?array $modes ): void { + $cache = ( new ReflectionClass( WPDO_Mode_Manager::class ) )->getProperty( 'cache' ); + $cache->setAccessible( true ); + $cache->setValue( null, $modes ); + } + + public function test_the_daily_tick_samples_entities_that_write_to_flat(): void { + self::set_modes( array( 'post' => 'disabled', 'user' => 'dual_write', 'term' => 'disabled', 'comment' => 'disabled' ) ); + try { + TMDO_Drift_Check::cron_tick(); + } finally { + self::set_modes( null ); + } + + $status = get_option( TMDO_Drift_Check::OPTION ); + $this->assertSame( TMDO_Drift_Check::CRON_SAMPLE, $status['user']['dc_grp']['sample'] ); + $this->assertSame( 2, $status['user']['dc_grp']['diff'], 'the sample covers all 7 fixture users' ); + $this->assertArrayNotHasKey( 'post', $status ); + } + + public function test_the_daily_tick_skips_entities_that_do_not_write_to_flat(): void { + self::set_modes( array( 'post' => 'disabled', 'user' => 'disabled', 'term' => 'disabled', 'comment' => 'disabled' ) ); + try { + TMDO_Drift_Check::cron_tick(); + } finally { + self::set_modes( null ); + } + + $this->assertFalse( get_option( TMDO_Drift_Check::OPTION ) ); + } + + public function test_the_daily_tick_runs_on_the_daily_health_check_hook(): void { + $source = (string) file_get_contents( WPDO_PLUGIN_DIR . 'includes/class-tmdo-core.php' ); + + $this->assertStringContainsString( "add_action( 'wpdo_daily_health_check', array( 'TMDO_Drift_Check', 'cron_tick' ) );", $source ); + } + public function test_rejects_an_unknown_group(): void { $this->expectException( InvalidArgumentException::class ); TMDO_Drift_Check::run( 'user', 'no_such_group' );