4 ) { throw new \LogicException( 'One bounded Overview provider registration is supported.' ); } foreach ( $capabilities as $capability ) { if ( ! is_string( $capability ) || ! preg_match( '/\A[a-z][a-z0-9_]{0,79}\z/D', $capability ) ) { throw new \InvalidArgumentException( 'Explicit native provider capabilities are required.' ); } } self::$provider = $provider; self::$provider_capabilities = array_values( array_unique( $capabilities ) ); } /** * Register the site overview page and its input validation hook. * * Integration calls this only after the installed candidate has been qualified. * * @return void */ public static function boot(): void { add_action( 'admin_menu', static function (): void { $hook = add_submenu_page( 'logged-ai', __( 'Logged.ai overview', 'logged-ai' ), __( 'Overview', 'logged-ai' ), 'logged_ai_view_events', self::PAGE, array( self::class, 'page' ) ); if ( is_string( $hook ) ) { add_action( 'load-' . $hook, array( self::class, 'load' ) ); } }, 12 ); } /** * Validate a finite overview interval and calculate its UTC calendar-day boundaries. * * Finite, explicit UTC calendar-day intervals; request input never selects a site. * * @param array $input Supplied bounded form or control fields to validate. * @param int $now Current Unix timestamp supplied for interval and retention decisions. * * @return array|\WP_Error Selected day count and inclusive/exclusive time boundaries, or an input error. */ public static function window( array $input, int $now ): array|\WP_Error { if ( array_diff( array_keys( $input ), array( 'page', 'days' ) ) || ( isset( $input['page'] ) && self::PAGE !== $input['page'] ) || ! in_array( $input['days'] ?? '7', array( '1', '7', '30' ), true ) || $now < 0 || $now >= 253402214400 ) { return new \WP_Error( 'overview_input', 'Choose a supported overview interval. Site scope has not changed.', array( 'status' => 400 ) ); } $days = (int) ( $input['days'] ?? '7' ); $today = intdiv( $now, 86400 ) * 86400; return array( 'days' => $days, 'from' => $today - ( $days - 1 ) * 86400, 'to' => $today + 86400, 'from_date' => gmdate( 'Y-m-d', $today - ( $days - 1 ) * 86400 ), 'to_date' => gmdate( 'Y-m-d', $today + 86400 ), ); } /** * Require current-site read access and supported GET inputs before admin output. * * Rejects unsupported inputs before WordPress emits the admin header. * * @return void */ public static function load(): void { $request_query = \LoggedAI\AdminInput::query(); $scope = Scope::current(); if ( is_network_admin() || is_wp_error( $scope ) ) { wp_die( esc_html__( 'This overview requires current site evidence access.', 'logged-ai' ), '', array( 'response' => 403 ) ); } if ( 'GET' !== \LoggedAI\AdminInput::method( 'GET' ) || is_wp_error( self::window( $request_query, time() ) ) ) { wp_die( esc_html__( 'Use a supported read-only overview interval.', 'logged-ai' ), '', array( 'response' => 400 ) ); } } /** * Add bounded canonical decimal counters without integer or float conversion. * * Exact decimal counters avoid float rounding or integer overflow in rollups. * * @param string $left Canonical decimal counter on the left side of the operation. * @param string $right Canonical decimal counter on the right side of the operation. * * @return string Exact decimal sum of the supplied counters. * * @throws \InvalidArgumentException When either counter is outside its canonical input grammar. */ public static function add_counts( string $left, string $right ): string { if ( ! preg_match( '/\A(?:0|[1-9][0-9]{0,22})\z/D', $left ) || ! preg_match( '/\A(?:0|[1-9][0-9]{0,19})\z/D', $right ) ) { throw new \InvalidArgumentException( 'Use bounded canonical counters.' ); } $result = ''; $carry = 0; $a = strlen( $left ) - 1; $b = strlen( $right ) - 1; while ( $a >= 0 || $b >= 0 || $carry ) { $sum = ( $a >= 0 ? (int) $left[ $a-- ] : 0 ) + ( $b >= 0 ? (int) $right[ $b-- ] : 0 ) + $carry; $result = (string) ( $sum % 10 ) . $result; $carry = intdiv( $sum, 10 ); } return $result; } /** * Validate a canonical unsigned 64-bit decimal counter string. * * @param mixed $value Supplied value to inspect without narrowing its native input contract. * * @return bool Whether the supplied string lies within the supported counter range. */ private static function counter( mixed $value ): bool { return is_string( $value ) && preg_match( '/\A(?:0|[1-9][0-9]{0,19})\z/D', $value ) && ( strlen( $value ) < 20 || strcmp( $value, '18446744073709551615' ) <= 0 ); } /** * Compare validated nonnegative decimal strings by length and lexical order. * * @param string $left Canonical decimal counter on the left side of the operation. * @param string $right Canonical decimal counter on the right side of the operation. * * @return bool Whether the left counter is no greater than the right counter. */ private static function count_at_most( string $left, string $right ): bool { return strlen( $left ) < strlen( $right ) || ( strlen( $left ) === strlen( $right ) && strcmp( $left, $right ) <= 0 ); } /** * Validate fixed event metadata columns and their exact current scope. * * Strict fixed-column types before any row enters a display or source seal. * * @param array $row Fixed-column native event metadata row to validate. * @param int $network Server-selected native network identifier. * @param int $site Server-selected native site identifier. * * @return bool Whether the row satisfies the bounded overview metadata contract. */ private static function event_row( array $row, int $network, int $site ): bool { if ( array_keys( $row ) !== explode( ',', self::EVENT_COLUMNS ) || ! self::counter( $row['id'] ) || '0' === $row['id'] || $row['network_id'] !== (string) $network || $row['site_id'] !== (string) $site || ! in_array( $row['severity'], array( 'info', 'notice', 'warning', 'high', 'critical' ), true ) ) { return false; } foreach ( array( 'uid', 'request_id', 'operation_id' ) as $key ) { if ( ! is_string( $row[ $key ] ) || ! preg_match( '/\A[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\z/D', $row[ $key ] ) ) { return false; } } foreach ( array( 'actor_id', 'subject_id', 'object_id' ) as $key ) { if ( ( null === $row[ $key ] && 'object_id' !== $key ) || self::counter( $row[ $key ] ) ) { continue; } return false; } if ( ! self::counter( $row['schema_version'] ) || '0' === $row['schema_version'] || ! self::count_at_most( $row['schema_version'], '65535' ) ) { return false; } foreach ( array( 'code' => 80, 'category' => 32, 'action' => 32, 'outcome' => 24, 'actor_kind' => 24, 'object_type' => 24, 'channel' => 24, 'processing_state' => 16, ) as $key => $limit ) { if ( ! is_string( $row[ $key ] ) || strlen( $row[ $key ] ) > $limit || ! preg_match( '/\A[a-z][a-z0-9_.]*\z/D', $row[ $key ] ) ) { return false; } } return self::instant( $row['occurred_at'], true ) && self::instant( $row['ingested_at'], true ); } /** * Validate a stored timestamp through the strict UTC display parser. * * @param mixed $value Supplied value to inspect without narrowing its native input contract. * @param bool $fractional Whether the stored timestamp includes six fractional-second digits. * * @return bool Whether the value is a supported second or fractional-second timestamp. */ private static function instant( mixed $value, bool $fractional ): bool { if ( ! is_string( $value ) || strlen( $value ) !== ( $fractional ? 26 : 19 ) ) { return false; } try { TimeDisplay::for_zone( 'UTC' )->instant( $fractional ? $value : $value . '.000000' ); return true; } catch ( \InvalidArgumentException $error ) { return false; } } /** * Validate bounded event order and summarize the latest examined significant records. * * Up to 50 examined rows, with a separate sentinel and no invented total. * * @param array $rows Bounded retained source rows to validate and summarize. * @param int $network Server-selected native network identifier. * @param int $site Server-selected native site identifier. * * @return array|\WP_Error Examined rows, significance sample and additional-row sentinel, or an input error. */ public static function events( array $rows, int $network, int $site ): array|\WP_Error { if ( ! array_is_list( $rows ) || count( $rows ) > 51 ) { return new \WP_Error( 'overview_events', 'The event scan exceeds its fixed bound.' ); } $seen = array(); $previous = null; foreach ( $rows as $row ) { if ( ! is_array( $row ) || ! self::event_row( $row, $network, $site ) || isset( $seen[ $row['id'] ] ) ) { return new \WP_Error( 'overview_events', 'Current event metadata is unavailable.' ); } if ( null !== $previous && ( strcmp( $row['occurred_at'], $previous['occurred_at'] ) > 0 || ( $row['occurred_at'] === $previous['occurred_at'] && ! self::count_at_most( $row['id'], $previous['id'] ) ) ) ) { return new \WP_Error( 'overview_events', 'Current event ordering is unavailable.' ); } $seen[ $row['id'] ] = true; $previous = $row; } $examined = array_slice( $rows, 0, 50 ); $significant = array_values( array_filter( $examined, static fn( array $row ): bool => in_array( $row['severity'], array( 'warning', 'high', 'critical' ), true ) ) ); return array( 'examined' => count( $examined ), 'has_more' => count( $rows ) > 50, 'significant_count' => count( $significant ), 'significant' => array_slice( $significant, 0, 10 ), 'latest' => $examined[0]['occurred_at'] ?? null, 'bound_events' => $examined, ); } /** * Validate bounded retained groups and reconcile their exact admission/status counters. * * Counts only stored admission and own-issuance counters in 12 retained groups. * * @param array $rows Bounded retained source rows to validate and summarize. * @param int $network Server-selected native network identifier. * @param int $site Server-selected native site identifier. * * @return array|\WP_Error Displayed groups, decimal totals and additional-group sentinel, or an input error. */ public static function groups( array $rows, int $network, int $site ): array|\WP_Error { if ( ! array_is_list( $rows ) || count( $rows ) > 13 ) { return new \WP_Error( 'overview_groups', 'The request-group scan exceeds its fixed bound.' ); } $seen = array(); $admitted = '0'; $own = '0'; $partial = '0'; $unknown = '0'; $previous = null; foreach ( $rows as $index => $row ) { if ( ! is_array( $row ) || array_keys( $row ) !== explode( ',', self::GROUP_COLUMNS ) || ! is_string( $row['group_key'] ) || ! preg_match( '/\A[a-f0-9]{64}\z/D', $row['group_key'] ) || isset( $seen[ $row['group_key'] ] ) || $row['network_id'] !== (string) $network || $row['site_id'] !== (string) $site || ! self::counter( $row['window_start'] ) || ! self::count_at_most( $row['window_start'], '253402300740' ) || 0 !== (int) $row['window_start'] % 60 || ! is_string( $row['source'] ) || ! preg_match( '/\A[a-z][a-z0-9_]{0,23}\z/D', $row['source'] ) || ! is_string( $row['family'] ) || ! preg_match( '/\A[a-z][a-z0-9_]{0,47}\z/D', $row['family'] ) || ! self::instant( $row['first_at'], false ) || ! self::instant( $row['last_at'], false ) ) { return new \WP_Error( 'overview_groups', 'Current request-group metadata is unavailable.' ); } // Admission buckets and capture timestamps can straddle a minute; native // writers do not guarantee first_at/last_at are chronological extrema. if ( null !== $previous && ( ! self::count_at_most( $row['window_start'], $previous['window_start'] ) || ( $row['window_start'] === $previous['window_start'] && strcmp( $row['group_key'], $previous['group_key'] ) <= 0 ) ) ) { return new \WP_Error( 'overview_groups', 'Current request-group ordering is unavailable.' ); } foreach ( array( 'admitted_count', 'partial_count', 'unknown_count', 'status_1xx', 'status_2xx', 'status_3xx', 'status_4xx', 'status_5xx', 'own_responses' ) as $key ) { if ( ! self::counter( $row[ $key ] ) || ! self::counter( $row['admitted_count'] ) || ! self::count_at_most( $row[ $key ], $row['admitted_count'] ) ) { return new \WP_Error( 'overview_groups', 'A retained request counter is inconsistent.' ); } } $statuses = $row['unknown_count']; foreach ( array( 'status_1xx', 'status_2xx', 'status_3xx', 'status_4xx', 'status_5xx' ) as $key ) { $statuses = self::add_counts( $statuses, $row[ $key ] ); } if ( $statuses !== $row['admitted_count'] ) { return new \WP_Error( 'overview_groups', 'Retained status counts do not reconcile with admitted observations.' ); } $seen[ $row['group_key'] ] = true; $previous = $row; if ( $index < 12 ) { $admitted = self::add_counts( $admitted, $row['admitted_count'] ); $own = self::add_counts( $own, $row['own_responses'] ); $partial = self::add_counts( $partial, $row['partial_count'] ); $unknown = self::add_counts( $unknown, $row['unknown_count'] ); } } return array( 'groups' => array_slice( $rows, 0, 12 ), 'has_more' => count( $rows ) > 12, 'admitted' => $admitted, 'own_responses' => $own, 'partial' => $partial, 'unknown' => $unknown, ); } /** * Check that the caller and private reader still match their captured native contexts. * * @param \wpdb $caller Original native database connection retained by its caller. * @param array $origin Original caller context captured before callbacks and queries. * @param WorkerConnection $reader Independent overview worker connection. * @param array $reader_origin Original native context captured for the private reader. * * @return bool Whether both connection contexts remain unchanged. */ private static function pinned( \wpdb $caller, array $origin, WorkerConnection $reader, array $reader_origin ): bool { return $GLOBALS['wpdb'] === $caller && Derived::native_context( $caller ) === $origin && Derived::native_context( $reader ) === $reader_origin; } /** * Open a private overview reader and establish its fixed current-read session. * * Independent autocommit reader; setup never executes against the caller. * * @param \wpdb $caller Original native database connection retained by its caller. * @param array $origin Original caller context captured before callbacks and queries. * @param Scope $scope Current native authorization scope selected for this operation. * * @return WorkerConnection Verified private worker connection. * * @throws \Throwable When private connection setup or its native context cannot be verified. * * @throws \RuntimeException When the private reader or its fixed session setup fails validation. */ private static function open( \wpdb $caller, array $origin, Scope $scope ): WorkerConnection { $reader = new WorkerConnection( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST ); try { $reader->set_prefix( $caller->base_prefix ); $reader->set_blog_id( (int) $caller->blogid, $scope->network_id ); $reader->suppress_errors( true ); $reader_origin = Derived::native_context( $reader ); $caller_map = $origin; $reader_map = $reader_origin; unset( $caller_map['blogid'], $caller_map['siteid'], $reader_map['blogid'], $reader_map['siteid'] ); if ( ! $reader->ready || $reader_map !== $caller_map || ! self::pinned( $caller, $origin, $reader, $reader_origin ) ) { throw new \RuntimeException( 'overview_connection' ); } foreach ( array( 'SET SESSION autocommit=1', 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'SET SESSION innodb_lock_wait_timeout=2' ) as $sql ) { if ( false === $reader->query( $sql ) || ! self::pinned( $caller, $origin, $reader, $reader_origin ) ) { throw new \RuntimeException( 'overview_connection' ); } } return $reader; } catch ( \Throwable $error ) { $reader->close(); throw $error; } } /** * Read bounded current overview metadata through the independent selected connection. * * No wide aggregate, payload, address, sample-ID or caller-global query. * * @param WorkerConnection $db Independent current-read connection owned by the overview. * @param Scope $scope Current native authorization scope selected for this operation. * @param array $window Validated UTC interval with inclusive and exclusive boundaries. * @param int $now Current Unix timestamp supplied for interval and retention decisions. * @param bool $locking Whether fixed source reads acquire row locks for final verification. * @param callable $current Callback rechecking captured native and provider context. * * @return array Fixed source snapshot with explicit unavailable event, group and request metadata. * * @throws \RuntimeException When protected metadata or the captured native context is unavailable. */ private static function read( WorkerConnection $db, Scope $scope, array $window, int $now, bool $locking, callable $current ): array { $lock = $locking ? ' FOR UPDATE' : ''; $names = HealthOverviewSnapshot::option_names(); $marks = implode( ',', array_fill( 0, count( $names ), '%s' ) ); if ( ! $current() ) { throw new \RuntimeException( 'overview_context' ); } $options = $db->get_results( $db->prepare( "SELECT option_name,CASE WHEN OCTET_LENGTH(option_value)<=8192 THEN option_value ELSE NULL END AS value,SHA2(option_value,256) AS binding FROM %i WHERE option_name IN ({$marks}) ORDER BY option_name{$lock}", array_merge( array( $db->options ), $names ) ), ARRAY_A ); if ( '' !== $db->last_error || ! is_array( $options ) || ! $current() ) { throw new \RuntimeException( 'overview_metadata' ); } $raw = array(); foreach ( $options as $row ) { if ( ! in_array( $row['option_name'] ?? null, $names, true ) || isset( $raw[ $row['option_name'] ] ) || ! is_string( $row['binding'] ?? null ) || ! preg_match( '/\A[a-f0-9]{64}\z/D', $row['binding'] ) ) { throw new \RuntimeException( 'overview_metadata' ); } $raw[ $row['option_name'] ] = array( 'value' => $row['value'], 'binding' => $row['binding'], ); } $policy = HealthOverviewSnapshot::policy( $raw ); $events = null; $groups = null; if ( ! is_wp_error( $policy ) ) { $from = gmdate( 'Y-m-d H:i:s', max( $window['from'], $now - $policy['event_days'] * 86400 ) ) . '.000000'; $until = gmdate( 'Y-m-d H:i:s', $window['to'] ) . '.000000'; $events = $db->get_results( $db->prepare( "SELECT id,uid,schema_version,network_id,site_id,occurred_at,ingested_at,code,category,action,outcome,severity,actor_kind,actor_id,subject_id,object_type,object_id,channel,request_id,operation_id,processing_state FROM %i WHERE network_id=%d AND site_id=%d AND occurred_at>=%s AND occurred_at<%s ORDER BY occurred_at DESC,id DESC LIMIT 51{$lock}", $db->base_prefix . 'logged_ai_events', $scope->network_id, $scope->site_id, $from, $until ), ARRAY_A ); if ( '' !== $db->last_error || ! is_array( $events ) ) { $events = null; } if ( ! $current() ) { throw new \RuntimeException( 'overview_context' ); } if ( is_array( $events ) ) { $events = array_map( array( Store::class, 'decode' ), $events ); } $groups = $db->get_results( $db->prepare( "SELECT group_key,network_id,site_id,window_start,source,family,first_at,last_at,admitted_count,partial_count,unknown_count,status_1xx,status_2xx,status_3xx,status_4xx,status_5xx,own_responses FROM %i WHERE network_id=%d AND site_id=%d AND window_start>=%d AND window_start<%d ORDER BY window_start DESC,group_key LIMIT 13{$lock}", $db->base_prefix . 'logged_ai_request_groups', $scope->network_id, $scope->site_id, max( $window['from'], $now - $policy['request_group_days'] * 86400 ), $window['to'] ), ARRAY_A ); if ( '' !== $db->last_error || ! is_array( $groups ) ) { $groups = null; } } if ( ! $current() ) { throw new \RuntimeException( 'overview_context' ); } $request_state = $db->get_row( $db->prepare( "SELECT network_id,site_id,schema_version,enabled,degraded FROM %i WHERE network_id=%d AND site_id=%d LIMIT 1{$lock}", $db->base_prefix . 'logged_ai_response_state', $scope->network_id, $scope->site_id ), ARRAY_A ); if ( '' !== $db->last_error || ! is_array( $request_state ) ) { $request_state = null; } if ( ! $current() ) { throw new \RuntimeException( 'overview_context' ); } $request_limits = $db->get_row( $db->prepare( "SELECT network_id,site_id,dropped,last_drop FROM %i WHERE network_id=%d AND site_id=%d LIMIT 1{$lock}", $db->base_prefix . 'logged_ai_request_limits', $scope->network_id, $scope->site_id ), ARRAY_A ); if ( '' !== $db->last_error || ! is_array( $request_limits ) ) { $request_limits = null; } if ( ! $current() ) { throw new \RuntimeException( 'overview_context' ); } $health = HealthOverviewSnapshot::project( $raw, $now, defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ); $health['requests'] = HealthOverviewSnapshot::requests( $request_state, $request_limits, $scope->network_id, $scope->site_id, Requests::VERSION ); return array( 'options' => $raw, 'events' => $events, 'groups' => $groups, 'request_state' => $request_state, 'request_limits' => $request_limits, 'health' => $health, ); } /** * Record unsupported query mutations while permitting known private setup statements. * * A read-only observer permits only the two known private connection setup paths. * * @param string $sql Native statement observed without changing its contents. * @param WorkerConnection $reader Independent overview worker connection. * @param bool $changed Mutable flag recording any unsupported observed mutation. * * @return string The original SQL, with the referenced drift flag updated when required. */ private static function observe( string $sql, WorkerConnection $reader, bool &$changed ): string { if ( preg_match( '/\A(?:SELECT|SHOW|DESCRIBE|EXPLAIN)\s/i', trim( $sql ) ) ) { return $sql; } $allowed = false; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace -- Inspect at most 32 argument-free frames to allow only the overview reader's own seal transaction or separate Scope setup during observed reads. $trace = debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 32 ); foreach ( $trace as $index => $frame ) { if ( 'query' !== $frame['function'] || ! ( $frame['object'] ?? null ) instanceof \wpdb ) { continue; } $next = $trace[ $index + 1 ] ?? array(); $allowed = ( $frame['object'] === $reader && 'START TRANSACTION' === trim( $sql ) && ( $next['class'] ?? '' ) === self::class && 'seal' === $next['function'] ) || ( $frame['object'] instanceof WorkerConnection && $frame['object'] !== $reader && in_array( trim( $sql ), array( 'SET SESSION autocommit=1', 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED' ), true ) && ( $next['class'] ?? '' ) === Scope::class && 'committed_capabilities' === $next['function'] ); break; } if ( ! $allowed ) { $changed = true; } return $sql; } /** * Lock and recheck rendered source dependencies, provider output and current grants. * * Current locks start only after output callbacks, so erasure can finish during rendering. * * @param WorkerConnection $reader Independent overview worker connection. * @param Scope $scope Current native authorization scope selected for this operation. * @param array $window Validated UTC interval with inclusive and exclusive boundaries. * @param array $expected Free source snapshot captured before rendering callbacks. * @param callable $current Callback rechecking captured native and provider context. * @param OverviewProvider|null $provider Optional active provider; null selects the Free-only overview. * @param array|null $extension Optional provider snapshot; null when no extension is active. * @param array $capabilities Explicit native capabilities required for the optional provider. * * @return bool Whether repeated source reads and all required authority remain current. */ private static function seal( WorkerConnection $reader, Scope $scope, array $window, array $expected, callable $current, ?OverviewProvider $provider, ?array $extension, array $capabilities ): bool { // Privacy workers acquire this lease before row locks. Hold it until reader close. if ( null !== $provider && ( ! $current() || '1' !== (string) $reader->get_var( $reader->prepare( 'SELECT GET_LOCK(%s,1)', Derived::lock_name() ) ) || '' !== $reader->last_error || ! $current() ) ) { return false; } if ( ! $current() || false === $reader->query( 'START TRANSACTION' ) || ! $current() ) { return false; } $before = self::read( $reader, $scope, $window, time(), true, $current ); if ( $before !== $expected || ! $scope->valid_for_all( array( 'logged_ai_view_events' ) ) || ! $current() ) { return false; } if ( is_array( $expected['events'] ) ) { $result = self::events( $expected['events'], $scope->network_id, $scope->site_id ); if ( ! is_wp_error( $result ) && ! Store::render_current( $scope, array( 'events' => $result['bound_events'] ), false, $reader ) ) { return false; } } if ( null !== $provider && ( self::provider_snapshot( $provider, $reader, $scope, $window, $before, $current ) !== $extension || ! $scope->valid_for_all( $capabilities ) || ! $current() ) ) { return false; } // Provider callbacks may have changed an earlier Free dependency and vice versa. if ( ! $current() || self::read( $reader, $scope, $window, time(), true, $current ) !== $expected || ! $current() ) { return false; } return ( null === $provider || ( self::provider_snapshot( $provider, $reader, $scope, $window, $expected, $current ) === $extension && $current() ) ) && $scope->valid_for_all( array_merge( array( 'logged_ai_view_events' ), null === $provider ? array() : $capabilities ) ) && $current(); } /** * Read and validate a bounded current extension snapshot against the Free sources. * * Same finite extension read before formatting and inside both final seal passes. * * @param OverviewProvider $provider Installed provider bound to the current overview request. * @param WorkerConnection $reader Independent overview worker connection. * @param Scope $scope Current native authorization scope selected for this operation. * @param array $window Validated UTC interval with inclusive and exclusive boundaries. * @param array $snapshot Current bounded Free or provider metadata selected by the caller. * @param callable $current Callback rechecking captured native and provider context. * * @return array Available unexpired provider snapshot within the encoded size bound. * * @throws \RuntimeException When provider context, availability, expiry or encoded size fails validation. * * @throws \JsonException When the provider result cannot be encoded for its size check. * * phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- The documented JsonException propagates from json_encode with JSON_THROW_ON_ERROR; the direct-throw count does not follow that call. */ private static function provider_snapshot( OverviewProvider $provider, WorkerConnection $reader, Scope $scope, array $window, array $snapshot, callable $current ): array { $events = is_array( $snapshot['events'] ) ? self::events( $snapshot['events'], $scope->network_id, $scope->site_id ) : null; $available = is_array( $events ); if ( ! $current() ) { throw new \RuntimeException( 'overview_provider_context' ); } $result = $provider->snapshot( $reader, $scope, $window + array( 'events_available' => $available ), $available ? array_slice( $snapshot['events'], 0, 50 ) : array(), time() ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- Use throwing encoding as a bounded provider-result validity guard; malformed/deep/nonencodable provider data must fail rather than be repaired for display. if ( ! $current() || is_wp_error( $result ) || 'available' !== ( $result['state'] ?? null ) || ! array_key_exists( 'valid_until', $result ) || ! self::provider_unexpired( $result ) || strlen( json_encode( $result, JSON_THROW_ON_ERROR ) ) > 262144 ) { throw new \RuntimeException( 'overview_provider_snapshot' ); } return $result; } /** * Check optional provider expiry after the final callback boundary. * * Pure expiry check after the last native callback and private connection close. * * @param array|null $snapshot Optional provider result whose valid-until timestamp is checked. * * @return bool Whether no expiry applies or its validated timestamp remains in the future. */ private static function provider_unexpired( ?array $snapshot ): bool { if ( null === $snapshot || null === $snapshot['valid_until'] ) { return true; } $expiry = $snapshot['valid_until']; if ( ! self::instant( $expiry, true ) ) { return false; } $clock = microtime( true ); $now = gmdate( 'Y-m-d H:i:s', (int) $clock ) . sprintf( '.%06d', (int) ( ( $clock - floor( $clock ) ) * 1000000 ) ); return $expiry > $now; } /** * Buffer overview output and release it only after current source and access verification. * * Buffer and current-source seal; connection closure never commits caller work. * * @return void * * phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.Missing -- The local RuntimeException signals are caught by the buffered-render guard and converted to the fixed unavailable response. */ public static function page(): void { $request_query = \LoggedAI\AdminInput::query(); global $wpdb; $caller = $wpdb; $origin = Derived::native_context( $caller ); $scope = Scope::current(); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This authorized read-only selection uses window to reject unknown keys and permit only fixed day choices; it does not select a site or mutate data. $window = self::window( $request_query, time() ); if ( is_network_admin() || is_wp_error( $scope ) || is_wp_error( $window ) || 'GET' !== \LoggedAI\AdminInput::method( 'GET' ) || Derived::native_context( $caller ) !== $origin ) { wp_die( esc_html__( 'Overview scope or interval is unavailable.', 'logged-ai' ), '', array( 'response' => 403 ) ); } $level = ob_get_level(); $reader = null; $observer = null; $changed = false; $valid = false; $html = null; $extension = null; $provider = self::$provider; $capabilities = self::$provider_capabilities; $active_provider = null; // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- The native output-buffer callback must accept and discard this parameter so unsealed evidence is never flushed. ob_start( static fn( string $buffer ): string => '' ); try { $reader = self::open( $caller, $origin, $scope ); $reader_origin = Derived::native_context( $reader ); $current = static fn(): bool => self::pinned( $caller, $origin, $reader, $reader_origin ) && self::$provider === $provider && self::$provider_capabilities === $capabilities; $observer = static function ( string $sql ) use ( $reader, &$changed ): string { return self::observe( $sql, $reader, $changed ); }; add_filter( 'query', $observer, PHP_INT_MAX ); if ( ! $current() || DB_NAME !== $reader->get_var( 'SELECT DATABASE()' ) || ! $current() || ! $scope->valid_for_all( array( 'logged_ai_view_events' ) ) ) { throw new \RuntimeException( 'overview_context' ); } $now = time(); $snapshot = self::read( $reader, $scope, $window, $now, false, $current ); if ( null !== $provider && $scope->valid_for_all( $capabilities ) ) { $active_provider = $provider; if ( ! $current() || '1' !== (string) $reader->get_var( $reader->prepare( 'SELECT GET_LOCK(%s,1)', Derived::lock_name() ) ) || '' !== $reader->last_error || ! $current() ) { throw new \RuntimeException( 'overview_provider_lease' ); } try { $extension = self::provider_snapshot( $provider, $reader, $scope, $window, $snapshot, $current ); } finally { if ( ! $current() || '1' !== (string) $reader->get_var( $reader->prepare( 'SELECT RELEASE_LOCK(%s)', Derived::lock_name() ) ) || '' !== $reader->last_error || ! $current() ) { throw new \RuntimeException( 'overview_provider_release' ); } } } nocache_headers(); self::render( $scope, $window, $snapshot, $now, $active_provider, $extension ); $html = ob_get_clean(); // Native query callbacks inside seal can change the captured observer flag. // Witness: evidence/overview/native/paired-370c04d-observer-correct-lock/receipt.json. $valid = is_string( $html ) && strlen( $html ) <= 524288 && ! $changed && $current() && self::seal( $reader, $scope, $window, $snapshot, $current, $active_provider, $extension, $capabilities ) && ! $changed && $current(); // @phpstan-ignore booleanNot.alwaysTrue (The final-seal native no-op write changes this captured reference without changing any bound source.) } catch ( \Throwable $error ) { $valid = false; } finally { while ( ob_get_level() > $level ) { ob_end_clean(); } if ( null !== $observer ) { remove_filter( 'query', $observer, PHP_INT_MAX ); } if ( $reader instanceof WorkerConnection ) { try { if ( ! $reader->close() ) { $valid = false; } } catch ( \Throwable $error ) { $valid = false; } } } if ( ! $valid || Derived::native_context( $caller ) !== $origin || self::$provider !== $provider || self::$provider_capabilities !== $capabilities || ! self::provider_unexpired( $extension ) ) { wp_die( esc_html__( 'Overview evidence, monitoring state or access changed. Reload this view.', 'logged-ai' ), '', array( 'response' => 403 ) ); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Free and the registered provider escape every dynamic fragment inside the suppressed buffer; the combined current-source seal precedes this sole release. echo $html; } /** * Format bounded activity, optional extension, monitoring and request-group sections. * * @param Scope $scope Current native authorization scope selected for this operation. * @param array $window Validated UTC interval with inclusive and exclusive boundaries. * @param array $snapshot Current bounded Free or provider metadata selected by the caller. * @param int $now Current Unix timestamp supplied for interval and retention decisions. * @param OverviewProvider|null $provider Optional active provider; null renders the Free-only overview. * @param array|null $extension Optional provider snapshot; null when no extension is active. * * @return void */ private static function render( Scope $scope, array $window, array $snapshot, int $now, ?OverviewProvider $provider, ?array $extension ): void { DesignOverview::render( $scope, $window, $snapshot, $now, $provider, $extension, static function ( array $health, TimeDisplay $time ): void { self::render_health( $health, $time ); } ); } /** * Render persistent collection, worker, retention and request-monitoring status. * * Separate persistent health section, unaffected by a quiet needs-review list. * * @param array $health Projected collection, worker, retention and request-monitoring status. * @param TimeDisplay $time Configured display-time helper for rendered timestamps. * * @return void */ private static function render_health( array $health, TimeDisplay $time ): void { echo '

' . esc_html__( 'Monitoring health', 'logged-ai' ) . '

'; $capture = $health['activity_capture']; $controls = $health['collection_controls']; $requests = $health['requests']; $request_status = 'unavailable' === $requests['state'] ? 'Request monitoring state unavailable.' : ( 'degraded' === $requests['state'] ? 'Recorded degradation: ' . str_replace( '_', ' ', $requests['reason'] ) . '.' : 'No recorded request degradation; completeness is not established.' ); $request_status .= 'unavailable' === $requests['admission'] ? ' Admission diagnostics unavailable.' : ' Retained admission diagnostics: ' . $requests['dropped'] . ' dropped observations' . ( null !== $requests['last_drop'] ? ', latest at ' . $time->text( $requests['last_drop'] ) : '' ) . '; this is not a total for the selected interval.'; $items = array( 'Activity capture' => ( 'degraded' === $capture['state'] ? __( 'Some activity may be incomplete. Review the explanation below or contact support.', 'logged-ai' ) : 'No recorded degradation; this does not prove capture completeness.' ) . ' Collection: ' . str_replace( '_', ' ', $controls['state'] ) . ( $controls['disabled_categories'] ? ' (' . implode( ', ', $controls['disabled_categories'] ) . ')' : '' ), 'Request visibility' => 'WordPress layer only. Static files, cache hits and upstream responses can be invisible. ' . $request_status, 'Background processing' => str_replace( '_', ' ', $health['runner'] ), 'Delivery' => self::heartbeat_text( $health['workers']['alerts'], $time ) . ' A worker return does not prove recipient receipt.', 'Retention' => ( 'configured' === $health['retention']['state'] ? 'Activity ' . $health['retention']['event_days'] . ' days; observations ' . $health['retention']['request_days'] . ' days; minute groups ' . $health['retention']['request_group_days'] . ' days. ' : 'Retention settings unavailable; evidence withheld. ' ) . 'Activity cleanup: ' . self::heartbeat_text( $health['workers']['retention'], $time ) . ' Request cleanup: ' . self::heartbeat_text( $health['workers']['request_retention'], $time ), ); foreach ( $items as $label => $value ) { echo '
' . esc_html( $label ) . '
' . esc_html( $value ) . '
'; } echo '

' . esc_html( $health['meaning'] ) . '

'; if ( 'degraded' === $capture['state'] && class_exists( Health::class ) ) { Health::collection_notice( $capture['reason'], false, $capture['at'] ); } echo '
'; } /** * Format a worker heartbeat without implying successful recipient delivery. * * @param array $heartbeat Projected worker status and last recorded timestamp. * @param TimeDisplay $time Configured display-time helper for rendered timestamps. * * @return string Human-readable last-worker status or an explicit missing-record message. */ private static function heartbeat_text( array $heartbeat, TimeDisplay $time ): string { return 'not_recorded' === $heartbeat['state'] ? 'No recent worker record.' : ( 'failed' === $heartbeat['state'] ? 'Last worker failed' : 'Last worker quantum returned' ) . ' at ' . $time->text( $heartbeat['at'] ) . ( null !== $heartbeat['code'] ? ' (' . str_replace( '_', ' ', $heartbeat['code'] ) . ')' : '' ) . '.'; } }