chore: initial snapshot of 2meet-data-optimizer v0.1.0
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
/**
|
||||
* TMDO_FSM_Automator — Opt-in automatic execution of FSM_Advisor PROMOTE
|
||||
* recommendations (v2.5.0 M13).
|
||||
*
|
||||
* Default OFF. When admin enables, runs daily at 04:30 UTC. For each module:
|
||||
* - Calls TMDO_FSM_Advisor::advise()
|
||||
* - Only auto-promotes when ALL 4 conditions true:
|
||||
* 1. wpdo_automator_enabled = 1
|
||||
* 2. module not in wpdo_automator_blacklist
|
||||
* 3. TMDO_Health_Cron::get_last_run() critical_count = 0 in last 7 days
|
||||
* (cool-off — system is healthy)
|
||||
* 4. ≥ 24h since this module's last automated promotion
|
||||
* - Destructive transitions (cutover→cleanup, cleanup→complete) are
|
||||
* NEVER automated — admin must manually invoke.
|
||||
*
|
||||
* Audit trail: every automator action writes wp_wpdo_audit op='automator_promoted'
|
||||
* + fires action `wpdo/automator_promoted` for downstream subscribers.
|
||||
*
|
||||
* @package WP_Data_Optimizer
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automator — stateless static API.
|
||||
*/
|
||||
class TMDO_FSM_Automator {
|
||||
|
||||
public const OPT_ENABLED = 'wpdo_automator_enabled';
|
||||
public const OPT_BLACKLIST = 'wpdo_automator_blacklist';
|
||||
public const OPT_LAST_ACTION = 'wpdo_automator_last_action';
|
||||
|
||||
/** Cool-off window after critical health alert. */
|
||||
private const COOL_OFF_DAYS = 7;
|
||||
|
||||
/** Minimum interval between automated promotions of the same module. */
|
||||
private const MIN_INTERVAL_HOURS = 24;
|
||||
|
||||
/** Transitions that are never auto-executed (destructive). */
|
||||
private const FORBIDDEN_TRANSITIONS = array(
|
||||
array( 'verify', 'cutover' ), // cutover starts reading from custom — needs manual sign-off.
|
||||
array( 'cutover', 'cleanup' ), // cleanup purges wp_*meta — irreversible without snapshot.
|
||||
array( 'cleanup', 'complete' ), // complete = no fallback path.
|
||||
);
|
||||
|
||||
/**
|
||||
* Cron handler.
|
||||
*
|
||||
* @return array {ok:bool, executed:int, skipped:int, errors:array}
|
||||
*/
|
||||
public static function run(): array {
|
||||
$result = array(
|
||||
'ok' => true,
|
||||
'executed' => 0,
|
||||
'skipped' => 0,
|
||||
'errors' => array(),
|
||||
'actions' => array(),
|
||||
);
|
||||
|
||||
// Pre-flight: enabled?
|
||||
if ( ! self::is_enabled() ) {
|
||||
$result['ok'] = false;
|
||||
$result['errors'][] = 'automator disabled';
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Pre-flight: cool-off?
|
||||
if ( ! self::cool_off_clear() ) {
|
||||
$result['ok'] = false;
|
||||
$result['errors'][] = 'cool-off active (critical health in last ' . self::COOL_OFF_DAYS . ' days)';
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Iterate modules.
|
||||
if ( ! class_exists( 'TMDO_Feature_Flags' ) || ! class_exists( 'TMDO_FSM_Advisor' ) ) {
|
||||
$result['ok'] = false;
|
||||
$result['errors'][] = 'dependencies missing';
|
||||
return $result;
|
||||
}
|
||||
|
||||
$blacklist = (array) get_option( self::OPT_BLACKLIST, array() );
|
||||
$last_actions = (array) get_option( self::OPT_LAST_ACTION, array() );
|
||||
|
||||
foreach ( TMDO_Feature_Flags::all() as $module => $current_state ) {
|
||||
// Blacklisted?
|
||||
if ( in_array( $module, $blacklist, true ) ) {
|
||||
++$result['skipped'];
|
||||
continue;
|
||||
}
|
||||
// Get advisor recommendation.
|
||||
$advice = TMDO_FSM_Advisor::advise( $module );
|
||||
if ( 'PROMOTE' !== ( $advice['action'] ?? '' ) || 'info' !== ( $advice['level'] ?? '' ) ) {
|
||||
++$result['skipped'];
|
||||
continue;
|
||||
}
|
||||
$next_state = (string) ( $advice['next_state'] ?? '' );
|
||||
if ( '' === $next_state ) {
|
||||
++$result['skipped'];
|
||||
continue;
|
||||
}
|
||||
// Forbidden destructive transition?
|
||||
if ( self::is_forbidden_transition( $current_state, $next_state ) ) {
|
||||
++$result['skipped'];
|
||||
continue;
|
||||
}
|
||||
// Recent action?
|
||||
if ( ! self::interval_clear( $module, $last_actions ) ) {
|
||||
++$result['skipped'];
|
||||
continue;
|
||||
}
|
||||
// All checks passed — execute.
|
||||
$set_result = TMDO_Feature_Flags::set( $module, $next_state );
|
||||
if ( true === $set_result ) {
|
||||
++$result['executed'];
|
||||
$result['actions'][] = array(
|
||||
'module' => $module,
|
||||
'from' => $current_state,
|
||||
'to' => $next_state,
|
||||
);
|
||||
$last_actions[ $module ] = gmdate( 'Y-m-d H:i:s' );
|
||||
if ( class_exists( 'TMDO_Logger' ) ) {
|
||||
TMDO_Logger::info(
|
||||
'automator_promoted',
|
||||
array(
|
||||
'module' => $module,
|
||||
'from' => $current_state,
|
||||
'to' => $next_state,
|
||||
)
|
||||
);
|
||||
}
|
||||
do_action( 'wpdo/automator_promoted', $module, $current_state, $next_state );
|
||||
} else {
|
||||
$result['errors'][] = "{$module}: " . ( $set_result instanceof \WP_Error ? $set_result->get_error_code() : 'unknown' );
|
||||
}
|
||||
}
|
||||
|
||||
update_option( self::OPT_LAST_ACTION, $last_actions, false );
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ─── settings accessors ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Return whether the automator is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_enabled(): bool {
|
||||
return '1' === (string) get_option( self::OPT_ENABLED, '0' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of blacklisted module slugs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function blacklist(): array {
|
||||
return (array) get_option( self::OPT_BLACKLIST, array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the map of module → last automated action timestamp.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function last_actions(): array {
|
||||
return (array) get_option( self::OPT_LAST_ACTION, array() );
|
||||
}
|
||||
|
||||
// ─── private helpers ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Cool-off: false when there's been a critical health alert in last N days.
|
||||
*
|
||||
* @return bool true = OK to act, false = blocked.
|
||||
*/
|
||||
private static function cool_off_clear(): bool {
|
||||
if ( ! class_exists( 'TMDO_Health_Cron' ) ) {
|
||||
return true; // No data — assume OK.
|
||||
}
|
||||
$last = TMDO_Health_Cron::get_last_run();
|
||||
if ( ! is_array( $last ) ) {
|
||||
return true;
|
||||
}
|
||||
$crit = (int) ( $last['critical_count'] ?? 0 );
|
||||
if ( 0 === $crit ) {
|
||||
return true;
|
||||
}
|
||||
$ts = isset( $last['ran_at'] ) ? strtotime( (string) $last['ran_at'] . ' UTC' ) : 0;
|
||||
if ( $ts <= 0 ) {
|
||||
return true;
|
||||
}
|
||||
// Critical alert exists; cool-off if within window.
|
||||
return ( time() - $ts ) > ( self::COOL_OFF_DAYS * DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-module interval: ≥ 24h since last automated action on this module.
|
||||
*
|
||||
* @param string $module Module slug.
|
||||
* @param array $last_actions Map of module → timestamp.
|
||||
* @return bool true = OK to act.
|
||||
*/
|
||||
private static function interval_clear( string $module, array $last_actions ): bool {
|
||||
$last = (string) ( $last_actions[ $module ] ?? '' );
|
||||
if ( '' === $last ) {
|
||||
return true;
|
||||
}
|
||||
$ts = strtotime( $last . ' UTC' );
|
||||
if ( $ts <= 0 ) {
|
||||
return true;
|
||||
}
|
||||
return ( time() - $ts ) >= ( self::MIN_INTERVAL_HOURS * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if (from, to) is in FORBIDDEN_TRANSITIONS.
|
||||
*
|
||||
* @param string $from Current state.
|
||||
* @param string $to Target state.
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_forbidden_transition( string $from, string $to ): bool {
|
||||
foreach ( self::FORBIDDEN_TRANSITIONS as $pair ) {
|
||||
if ( $pair[0] === $from && $pair[1] === $to ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user