fix(2meet-data-optimizer): stop false alarms in doctor, term-comment-diagnose and bridge-status

- doctor: entity flat tables come from the registry (TMDO_CLI::check_entity_tables()) instead of a hard-coded user list naming the never-registered sso group, which failed doctor on every site; it also reports Promotion Guard violations and the recorded drift status per entity (report_guard_and_drift()).
- term-comment-diagnose: filter status comes from each filter's is_enabled() (the garbage filter and misc bucket default to off but were shown enabled); a filter whose add-on is not active shows "not installed".
- bridge-status: flat_rows counts rows holding a value (wp_core keeps an all-NULL row per post: 15306% on dev36) and eav_ids of a post-type group counts only its post types.
- Tests: the WP_CLI stubs record their output in $GLOBALS['_wp_cli_output'].
- Verify: DoctorTest, CliFilterStatusTest, CliBridgeStatusTest (RED before); dev36 doctor exit 1 → 0, wp_core 15306% → 100%.
- PR: PR-076
This commit is contained in:
2026-09-27 01:00:13 +08:00
parent 9d1a7c5591
commit f15473527c
8 changed files with 352 additions and 46 deletions
+13 -9
View File
@@ -442,9 +442,9 @@ class TMDO_CLI_Term_Comment {
WP_CLI::log( '' );
WP_CLI::log( '── Filter chain status ──' );
self::print_filter_status( 'wpdo_term_comment_garbage_filter_enabled', 'Garbage filter (Phase 1)' );
self::print_filter_status( 'wpdo_wc_term_count_filter_enabled', 'WC term count filter (Phase 3)' );
self::print_filter_status( 'wpdo_term_comment_misc_bucket_enabled', 'Misc bucket (Phase 4)' );
self::print_filter_status( 'TMDO_Term_Comment_Garbage_Filter', 'Garbage filter (Phase 1)' );
self::print_filter_status( 'TMDO_WC_Term_Count_Filter', 'WC term count filter (Phase 3)' );
self::print_filter_status( 'TMDO_Term_Comment_Misc_Bucket', 'Misc bucket (Phase 4)' );
WP_CLI::log( '' );
WP_CLI::log( 'Recommendation: ' . self::recommend_next_action( $term_mode, $comment_mode ) );
@@ -504,15 +504,19 @@ class TMDO_CLI_Term_Comment {
}
/**
* Print a filter's enabled/disabled status.
* Print a filter's enabled/disabled status, as the filter itself decides it
* (each has its own default).
*
* @param string $option_key Option storing the toggle value.
* @param string $label Human-readable name.
* @param string $filter_class Filter class with a static is_enabled().
* @param string $label Human-readable name.
* @return void
*/
private static function print_filter_status( string $option_key, string $label ): void {
$enabled = (bool) get_option( $option_key, '1' );
WP_CLI::log( sprintf( '%-32s%s', $label . ':', $enabled ? '✓ enabled' : '✗ disabled' ) );
private static function print_filter_status( string $filter_class, string $label ): void {
if ( ! class_exists( $filter_class ) ) {
WP_CLI::log( sprintf( '%-32s%s', $label . ':', '— not installed' ) );
return;
}
WP_CLI::log( sprintf( '%-32s%s', $label . ':', $filter_class::is_enabled() ? '✓ enabled' : '✗ disabled' ) );
}
// ── term-comment-shadow-report (v2.12.5 Phase 5) ──────────────────────────
+13 -6
View File
@@ -91,22 +91,29 @@ class TMDO_CLI_V2 {
$wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $flat_table )
);
$flat_count = $flat_exists
// Flat rows holding at least one value (wp_core keeps an all-NULL row per post).
$managed_keys = TMDO_Entity_Registry::get_group_keys( $entity_type, $group_name );
$has_value = implode( ' OR ', array_map( static fn( $k ) => '`' . TMDO_Schema_Manager::sanitize_column_name( $k ) . '` IS NOT NULL', $managed_keys ) );
$flat_count = $flat_exists && '' !== $has_value
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
? (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$flat_table}`" )
? (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$flat_table}` WHERE {$has_value}" )
: 0;
// EAV source: count distinct entity IDs that have at least one managed key.
$managed_keys = TMDO_Entity_Registry::get_group_keys( $entity_type, $group_name );
$eav_table = $eav_tables[ $entity_type ] ?? '';
$eav_count = 0;
$eav_table = $eav_tables[ $entity_type ] ?? '';
$eav_count = 0;
if ( '' !== $eav_table && ! empty( $managed_keys ) ) {
$id_col = ( 'post' === $entity_type ) ? 'post_id' : "{$entity_type}_id";
$placeholders = implode( ', ', array_fill( 0, count( $managed_keys ), '%s' ) );
// $id_col comes from a hardcoded map; $eav_table is $wpdb->*meta (framework-managed); $placeholders is %s repeats only.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
$sql = "SELECT COUNT(DISTINCT `{$id_col}`) FROM `{$eav_table}` WHERE `meta_key` IN ({$placeholders})";
$sql = "SELECT COUNT(DISTINCT m.`{$id_col}`) FROM `{$eav_table}` m WHERE m.`meta_key` IN ({$placeholders})";
// A group of some post types only: other posts holding its keys are not its rows.
$post_types = 'post' === $entity_type ? TMDO_Entity_Registry::get_group_post_types( 'post', $group_name ) : array();
if ( $post_types ) {
$sql .= " AND m.post_id IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "') )";
}
$eav_count = (int) $wpdb->get_var( $wpdb->prepare( $sql, ...$managed_keys ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
}
+82 -23
View File
@@ -94,6 +94,83 @@ class TMDO_CLI {
WP_CLI::success( 'Tables installed. DB version: ' . get_option( 'wpdo_db_version' ) );
}
/**
* Report the flat table of every backfillable group of every entity type,
* plus the installer's migration status and points ledger tables.
*
* @return bool Every table exists.
*/
public static function check_entity_tables(): bool {
global $wpdb;
$tables = array( $wpdb->prefix . 'wpdo_migration_status', $wpdb->prefix . 'wpdo_user_points_ledger' );
foreach ( TMDO_Entity_Registry::VALID_ENTITY_TYPES as $entity_type ) {
foreach ( TMDO_Entity_Migration_Engine::backfillable_groups( $entity_type ) as $group ) {
$tables[] = TMDO_Schema_Manager::get_table_name( $entity_type, $group );
}
}
$ok = true;
foreach ( $tables as $t ) {
if ( TMDO_IS_SQLITE ) {
$exists = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=%s", $t ) );
} else {
$exists = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $t ) );
}
if ( $exists ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$t}`" );
WP_CLI::log( " [OK] {$t} ({$rows} rows)" );
} else {
WP_CLI::warning( " [MISSING] {$t} — run `wp wpdo install` to create." );
$ok = false;
}
}
return $ok;
}
/**
* Report Promotion Guard violations and the recorded drift status of every
* entity that writes flat tables or has been checked (informational).
*/
public static function report_guard_and_drift(): void {
WP_CLI::log( '' );
$violations = TMDO_Promotion_Guard::violations();
if ( empty( $violations ) ) {
WP_CLI::log( ' [OK] Promotion Guard: no violation' );
}
foreach ( $violations as $entity_type => $integrations ) {
WP_CLI::warning( " [GUARD] {$entity_type} is aeav_only while " . implode( ', ', $integrations ) . " reads native meta — write flat back with `wp tmdo bridge-restore-native {$entity_type} --demote`." );
}
$status = get_option( TMDO_Drift_Check::OPTION, array() );
$status = is_array( $status ) ? $status : array();
WP_CLI::log( 'Drift (last `wp tmdo drift-check` or daily sample):' );
foreach ( TMDO_Entity_Registry::VALID_ENTITY_TYPES as $entity_type ) {
$groups = TMDO_Entity_Migration_Engine::backfillable_groups( $entity_type );
$last = $status[ $entity_type ] ?? array();
if ( empty( $groups ) || ( empty( $last ) && ! TMDO_Mode_Manager::writes_to_flat( $entity_type ) ) ) {
continue;
}
if ( empty( $last ) ) {
WP_CLI::log( " {$entity_type}: never checked — run `wp tmdo drift-check {$entity_type}`" );
continue;
}
$drifting = array();
foreach ( $last as $group => $s ) {
if ( (int) $s['miss'] + (int) $s['diff'] + (int) $s['extra'] > 0 ) {
$drifting[] = sprintf( '%s miss=%d diff=%d extra=%d', $group, $s['miss'], $s['diff'], $s['extra'] );
}
}
$oldest = min( array_column( $last, 'checked_at' ) );
if ( $drifting ) {
WP_CLI::warning( " {$entity_type}: " . implode( '; ', $drifting ) . " (checked {$oldest} UTC or later) — `wp tmdo drift-check {$entity_type} --fix`" );
} else {
WP_CLI::log( sprintf( ' [OK] %s: %d group(s) clean (oldest check %s UTC)', $entity_type, count( $last ), $oldest ) );
}
}
}
/**
* Run health check on all tables.
*
@@ -195,30 +272,12 @@ class TMDO_CLI {
WP_CLI::log( $exists ? " [OK] {$t}" : " [NOT CREATED] {$t} (will be created on first use)" );
}
// Check user entity flat tables (v2.5.5+).
$user_entity_tables = array(
$wpdb->prefix . 'wpdo_user_membership',
$wpdb->prefix . 'wpdo_user_activity',
$wpdb->prefix . 'wpdo_user_profile',
$wpdb->prefix . 'wpdo_user_sso',
$wpdb->prefix . 'wpdo_user_points_ledger',
$wpdb->prefix . 'wpdo_migration_status',
);
foreach ( $user_entity_tables as $t ) {
if ( TMDO_IS_SQLITE ) {
$exists = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=%s", $t ) );
} else {
$exists = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $t ) );
}
if ( $exists ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$t}`" );
WP_CLI::log( " [OK] {$t} ({$rows} rows)" );
} else {
WP_CLI::warning( " [MISSING] {$t} — run \`wp wpdo install\` to create." );
$all_ok = false;
}
// Entity flat tables, from the registry (was a hard-coded user list that
// named the never-registered sso group).
if ( ! self::check_entity_tables() ) {
$all_ok = false;
}
self::report_guard_and_drift();
// Check partner plugin custom tables via Custom Table Registry (v2.6.0+).
if ( class_exists( 'TMDO_Custom_Table_Registry' ) ) {