fetch( $url ); if ( null === $payload ) { return $this->finish( false, __( 'Registry unreachable.', 'agentyllo' ) ); } $signature = $this->fetch( $url . '.sig' ); if ( null === $signature ) { return $this->finish( false, __( 'Registry signature unavailable.', 'agentyllo' ) ); } if ( ! $this->verifier->verify( $payload, $signature ) ) { return $this->finish( false, __( 'Registry signature verification failed — manifest rejected.', 'agentyllo' ) ); } $decoded = json_decode( $payload, true ); if ( ! is_array( $decoded ) || (int) ( $decoded['schema'] ?? 0 ) !== 1 || ! isset( $decoded['providers'] ) ) { return $this->finish( false, __( 'Registry manifest malformed.', 'agentyllo' ) ); } $incoming = (int) ( $decoded['sequence'] ?? 0 ); $current = $this->manifest->sequence(); if ( $incoming < $current ) { return $this->finish( false, __( 'Registry rollback rejected (older sequence).', 'agentyllo' ), $incoming ); } update_option( Manifest::OPTION, array( 'payload' => $decoded, 'sequence' => $incoming, 'synced_at' => time(), ), false ); $this->manifest->reset(); return $this->finish( true, __( 'Registry synced.', 'agentyllo' ), $incoming ); } /** * Last sync status for the UI. * * @return array{ok: bool, message: string, sequence: int, at: int} */ public function status(): array { $stored = get_option( self::STATUS_OPTION ); if ( ! is_array( $stored ) ) { return array( 'ok' => false, 'message' => '', 'sequence' => 0, 'at' => 0, ); } return array( 'ok' => (bool) ( $stored['ok'] ?? false ), 'message' => (string) ( $stored['message'] ?? '' ), 'sequence' => (int) ( $stored['sequence'] ?? 0 ), 'at' => (int) ( $stored['at'] ?? 0 ), ); } /** * GET a small text resource; null on any failure. * * @param string $url URL. */ private function fetch( string $url ): ?string { $response = wp_safe_remote_get( $url, array( 'timeout' => self::TIMEOUT, 'redirection' => 2, 'limit_response_size' => self::MAX_BYTES, 'user-agent' => 'Agentyllo (+https://www.agentyllo.com)', 'headers' => array( 'Accept' => 'application/json, text/plain' ), ) ); if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { return null; } $body = (string) wp_remote_retrieve_body( $response ); return '' === $body ? null : $body; } /** * Persist and return the sync outcome. * * @param bool $ok Success. * @param string $message Message. * @param int $sequence Sequence seen. * @return array{ok: bool, message: string, sequence: int} */ private function finish( bool $ok, string $message, int $sequence = 0 ): array { update_option( self::STATUS_OPTION, array( 'ok' => $ok, 'message' => $message, 'sequence' => $sequence, 'at' => time(), ), false ); return array( 'ok' => $ok, 'message' => $message, 'sequence' => $sequence, ); } }