# woocommerce-pos/1.10.7/includes/Sync/Pos_Uuid.php

WCPOS – Point of Sale (POS) plugin for WooCommerce, version 1.10.7. 844 lines.

- Page: https://pluginprobe.com/plugins/woocommerce-pos/1.10.7/code/includes/Sync/Pos_Uuid.php
- Raw: https://pluginprobe.com/plugins/woocommerce-pos/1.10.7/raw/includes/Sync/Pos_Uuid.php
- Modified: 2026-08-30T08:44:36+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/woocommerce-pos/1.10.7/code/includes/Sync/Pos_Uuid.php#L10-L20`.

```php
<?php
/**
 * WCPOS sync read surface.
 *
 * @package WCPOS\WooCommercePOS\Sync
 */

namespace WCPOS\WooCommercePOS\Sync;

use Exception;
use Ramsey\Uuid\Uuid;
use WC_Customer;
use WC_Order_Item;
use WCPOS\WooCommercePOS\Logger;

// phpcs:disable Squiz.Commenting, Generic.Commenting -- Ported lab documentation is preserved verbatim.
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Queries are prepared before execution.

/**
 * Uniform record identity on the server side — the sole authority that stamps,
 * validates, deduplicates, and checks ownership of `_woocommerce_pos_uuid`.
 * Legacy API callers delegate here (ADR 0021, decision c).
 *
 * The client uses this uuid as the stable RxDB primary key (ADR 0008, guardrail
 * G1): a record carries the SAME identity on the server and the client, may be
 * born on either side, and is NEVER re-keyed. This is the server half of that
 * contract — a record pulled from the lab namespace arrives WITH its uuid, so the
 * client never has to mint a divergent one for a server-born record. Multisite
 * customer first-stamps are serialized while adopting legacy per-blog values.
 *
 * Reads an existing valid uuid from the record's meta; if absent/invalid it
 * generates one and PERSISTS it (so it is stable across pulls), then mirrors it
 * into the serialized payload's `meta_data`. Duck-typed on the WC_Data methods so
 * it stays unit-testable without WooCommerce loaded. UUID convergence does not
 * use an object-cache lock; sync writes are serialized by their record lock,
 * while stamping deterministically converges duplicate meta rows.
 */
class Pos_Uuid {
	public const META_KEY = Api::UUID_META_KEY;
	/** Meta key carrying the freshly-recomputed variable-product price range (P2-2). */

	/**
	 * A standard 8-4-4-4-12 uuid shape (any version), case-insensitive.
	 */
	public static function is_uuid( $value ): bool {
		return \is_string( $value )
			 && (bool) preg_match( '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $value );
	}

	/**
	 * The first VALID uuid among a record's meta entries (WC_Meta_Data objects
	 * with ->key/->value, or arrays), or '' if none. Skips blank / invalid / a
	 * blank duplicate in favour of a later valid one.
	 */
	public static function read_valid_uuid_from_meta( array $meta_data ): string {
		$entry = self::first_valid_uuid_entry( $meta_data );

		return null === $entry ? '' : (string) Meta_Entry::value( $entry );
	}

	/**
	 * The record's CANONICAL uuid entry — the first meta entry carrying a valid
	 * uuid — or null. One selection rule for every reader: the served value, the
	 * entry the prune keeps and the provenance check all name the same entry.
	 *
	 * @return mixed|null
	 */
	private static function first_valid_uuid_entry( array $meta_data ) {
		foreach ( $meta_data as $meta ) {
			if ( self::META_KEY === Meta_Entry::key( $meta ) && self::is_uuid( Meta_Entry::value( $meta ) ) ) {
				return $meta;
			}
		}

		return null;
	}

	/**
	 * Ensure the record carries a stable, UNIQUELY-OWNED uuid: reuse a valid
	 * existing one, else generate + persist a new one. Returns the uuid (or '' if
	 * the object can't carry meta). Duck-typed on get_meta_data / update_meta_data
	 * / save_meta_data.
	 *
	 * $opts['collides'] is an optional callable (uuid, object) => bool: when it
	 * reports the existing uuid is already owned by ANOTHER record (a clone/import
	 * that copied the meta), we treat it as needing a fresh one rather than serving
	 * a duplicate RxDB key. Injected so the branching stays unit-testable; the live
	 * wiring uses the $wpdb-backed self::uuid_owned_by_other.
	 *
	 * $opts['trust_persisted'] (default false) settles ownership WITHOUT the
	 * detector when the uuid was loaded from this record's own meta row and is
	 * unchanged ({@see is_own_persisted_uuid}) — the ordinary save and read paths,
	 * where the detector re-proved a fact the row already stated at a cost linear
	 * in catalog size (#1805, ADR 0038). Leave it off where a loaded duplicate MUST
	 * be re-keyed: the collision backfill, the proxy stamper's in-response
	 * duplicates, and the V1 list lanes, whose no-shared-uuid-per-response contract
	 * has no other check.
	 *
	 * @param mixed $object
	 */
	public static function ensure_uuid( $object, array $opts = array() ): string {
		if ( ! \is_object( $object ) || ! method_exists( $object, 'get_meta_data' ) ) {
			return '';
		}
		if (
			$object instanceof WC_Customer
			&& \function_exists( 'is_multisite' )
			&& is_multisite()
			&& '' === self::read_valid_uuid_from_meta( (array) $object->get_meta_data() )
		) {
			return self::ensure_multisite_customer_uuid( $object, $opts );
		}

		return self::ensure_uuid_without_user_lock( $object, $opts );
	}

	/**
	 * Ensure a UUID after any customer-specific coordination has completed.
	 *
	 * @param mixed $object
	 */
	private static function ensure_uuid_without_user_lock( $object, array $opts ): string {
		$collides = $opts['collides'] ?? null;
		$persist  = $opts['persist'] ?? true;
		$trust    = ! empty( $opts['trust_persisted'] );
		$entry    = self::first_valid_uuid_entry( (array) $object->get_meta_data() );
		$existing = null === $entry ? '' : (string) Meta_Entry::value( $entry );
		if ( '' !== $existing ) {
			$owned = ! \is_callable( $collides )
				|| ( $trust && self::is_own_persisted_uuid( $object, $entry ) )
				|| ! $collides( $existing, $object );
			if ( $owned ) {
				// Converge any duplicate uuid metas (e.g. a concurrent first-stamp) to
				// the single canonical value — deterministic regardless of object-cache
				// backend, so no cross-request lock is required for correctness.
				self::prune_duplicate_uuid_meta( $object, $persist );

				return $existing;
			}
		}
		if ( ! method_exists( $object, 'update_meta_data' ) ) {
			return '';
		}
		// Minting persists by default: a freshly-generated uuid that isn't written
		// back would differ on the next pull, making identity unstable — worse than
		// none. persist:false is for a BEFORE-save hook, where the in-progress save
		// writes the meta, so we add it but skip a redundant second save.
		if ( $persist && ! method_exists( $object, 'save_meta_data' ) ) {
			return '';
		}
		$uuid = self::generate_uuid();
		if ( '' !== $existing ) {
			// A re-key changes the record's client-side primary key (ADR 0038). Rare
			// and consequential, so it is always on the record: which record, the
			// identity it lost, the one it received.
			// The commonest re-key is an unsaved clone (id 0), so the name is what
			// identifies it after the fact.
			$name = method_exists( $object, 'get_name' ) ? (string) $object->get_name() : '';
			Logger::log(
				sprintf(
					'Re-keyed %s #%d%s: uuid %s is already owned by another record; it now carries %s.',
					\get_class( $object ),
					method_exists( $object, 'get_id' ) ? (int) $object->get_id() : 0,
					'' === $name ? '' : ' (' . $name . ')',
					$existing,
					$uuid
				)
			);
		}
		$object->update_meta_data( self::META_KEY, $uuid );
		if ( $persist ) {
			call_user_func( array( $object, 'save_meta_data' ) );
			// A concurrent first-stamp may have persisted its own uuid between our
			// read and save. Re-read and converge on the first-valid row so every
			// racer returns the SAME winner instead of each serving its own mint.
			if ( $object instanceof \WC_Data ) {
				$object->read_meta_data( true );
				self::prune_duplicate_uuid_meta( $object, true );
				$stored = self::read_valid_uuid_from_meta( (array) $object->get_meta_data() );
				if ( self::is_uuid( $stored ) ) {
					return $stored;
				}
			}
		}

		return $uuid;
	}

	/**
	 * Legacy WP_User adapter for the shared WC_Data identity path (ADR 0021).
	 *
	 * @param mixed $user WP_User-like object or numeric user id.
	 */
	public static function ensure_user_uuid( $user ): string {
		$user_id = \is_object( $user ) && isset( $user->ID ) ? (int) $user->ID : (int) $user;
		if ( $user_id <= 0 || ! class_exists( WC_Customer::class ) ) {
			return '';
		}

		try {
			$customer = new WC_Customer( $user_id );
		} catch ( Exception $e ) {
			Logger::log( 'Unable to load customer for UUID stamping: ' . $e->getMessage() );
			return '';
		}

		if ( ! method_exists( $customer, 'get_id' ) || $user_id !== (int) $customer->get_id() ) {
			return '';
		}

		// No `trust_persisted` here: V1's customer list has no in-response
		// duplicate check, so its "no two records share a uuid" contract rests on
		// this detector (Test_Customers_Controller::test_customer_uuid_is_unique).
		return self::ensure_uuid(
			$customer,
			array( 'collides' => array( __CLASS__, 'uuid_owned_by_other_user' ) )
		);
	}

	/**
	 * Ensure the WC_Order_Item has a valid UUID.
	 *
	 * @param WC_Order_Item $item The order item object.
	 * @return void
	 */
	public static function ensure_order_item_uuid( WC_Order_Item $item ): void {
		global $wpdb;

		if ( self::is_uuid( $item->get_meta( self::META_KEY ) ) ) {
			return;
		}

		$lock_key = 'wc_pos_uuid_order_item_' . $item->get_id();
		$acquired = $wpdb->get_var( $wpdb->prepare( 'SELECT GET_LOCK(%s, %d)', $lock_key, 10 ) );
		if ( '1' !== (string) $acquired ) {
			Logger::log( 'Unable to acquire lock for order item UUID update for order item id ' . $item->get_id() );
			return;
		}
		try {
			// Persist any pending meta, then check the STORED uuid directly —
			// a full read_meta_data(true) reload would clobber sibling in-memory
			// meta on lanes where the datastore cache lags (HPOS misc `_sku`).
			$item->save_meta_data();
			$uuid = wc_get_order_item_meta( $item->get_id(), self::META_KEY, true );
			if ( ! self::is_uuid( $uuid ) ) {
				$uuid = Uuid::uuid4()->toString();
				$item->update_meta_data( self::META_KEY, $uuid );
				$item->save_meta_data();
			} elseif ( $uuid !== $item->get_meta( self::META_KEY ) ) {
				// A concurrent request minted first; converge the stale in-memory
				// item on the stored winner so the served payload carries it.
				$item->update_meta_data( self::META_KEY, $uuid );
			}
		} finally {
			$wpdb->get_var( $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $lock_key ) );
		}
	}

	/**
	 * Promote a legacy per-blog cashier uuid to the network-wide key.
	 *
	 * Before identity consolidated here, the cashier endpoint minted
	 * `_woocommerce_pos_uuid_{blog_id}` on multisite while every other reader used
	 * the plain network-wide key — forking one user into two RxDB identities. When
	 * the plain key holds no valid uuid yet, adopt the current blog's legacy value
	 * so existing multisite cashiers keep their identity regardless of which
	 * endpoint reads them first. An existing valid plain uuid wins, because it is
	 * what /customers has already served to clients. Legacy rows are left in place
	 * (harmless, rollback-safe). A legacy value owned by ANOTHER user — under the
	 * network key or the current blog's legacy key — is never adopted: the first
	 * reader must not claim a shared legacy uuid as their network identity
	 * (#1465). When two users share the same legacy value, neither adopts it and
	 * both are minted fresh; the 1.10.0 migration resolves the ambiguity for
	 * users it reaches first.
	 *
	 * @param WC_Customer $customer Customer being stamped.
	 */
	private static function adopt_legacy_multisite_user_uuid( WC_Customer $customer ): void {
		$existing = self::read_valid_uuid_from_meta( (array) $customer->get_meta_data() );
		if ( self::is_uuid( $existing ) ) {
			return;
		}

		$user_id = (int) $customer->get_id();
		$legacy = get_user_meta( $user_id, self::META_KEY . '_' . get_current_blog_id(), true );
		if ( self::is_uuid( $legacy ) && ! self::legacy_uuid_owned_by_other_user( $legacy, $customer ) ) {
			// Never clobber a uuid another request persisted concurrently — its
			// client is already keyed on it. A unique add when no row exists; a
			// compare-and-swap against the OBSERVED invalid value otherwise, so a
			// lock-timeout fallback that replaced the corrupt row between our
			// read and this write survives (the CAS no-ops and ensure_uuid then
			// serves the concurrent winner).
			$stored_rows = get_user_meta( $user_id, self::META_KEY, false );
			if ( array() === $stored_rows ) {
				add_user_meta( $user_id, self::META_KEY, $legacy, true );
			} elseif ( ! self::is_uuid( $stored_rows[0] ) ) {
				update_user_meta( $user_id, self::META_KEY, $legacy, $stored_rows[0] );
			}
		}
	}

	/**
	 * Serialize first-stamp and legacy adoption for one multisite customer.
	 */
	private static function ensure_multisite_customer_uuid( WC_Customer $customer, array $opts ): string {
		global $wpdb;

		$user_id   = (int) $customer->get_id();
		$lock_name = 'wcpos_user_uuid_' . $user_id;
		$acquired  = $wpdb->get_var( $wpdb->prepare( 'SELECT GET_LOCK(%s, %d)', $lock_name, 5 ) );
		if ( '1' !== (string) $acquired ) {
			// The lock holder is (or was) stamping this user. This uuid is the
			// client's RxDB primary key, so never serve '' — and when a legacy
			// identity may be mid-adoption, serve it read-only rather than writing
			// anything that would pre-empt the adoption and fork the user.
			$customer->read_meta_data( true );
			$persisted = self::read_valid_uuid_from_meta( (array) $customer->get_meta_data() );
			if ( self::is_uuid( $persisted ) ) {
				return $persisted;
			}

			// An adoptable legacy uuid is what the holder will promote — serve the
			// same value read-only so this response and the adoption agree.
			$legacy = get_user_meta( $user_id, self::META_KEY . '_' . get_current_blog_id(), true );
			if ( self::is_uuid( $legacy ) && ! self::legacy_uuid_owned_by_other_user( $legacy, $customer ) ) {
				return $legacy;
			}

			// First stamp under contention: persist a fallback without clobbering a
			// concurrent winner — a unique add when no row exists, a compare-and-swap
			// against the (invalid) first row otherwise — then serve whichever row
			// stuck so every racer converges on one persisted identity.
			$fallback    = self::generate_uuid();
			$stored_rows = get_user_meta( $user_id, self::META_KEY, false );
			if ( array() === $stored_rows ) {
				add_user_meta( $user_id, self::META_KEY, $fallback, true );
			} elseif ( ! self::is_uuid( $stored_rows[0] ) ) {
				update_user_meta( $user_id, self::META_KEY, $fallback, $stored_rows[0] );
			}
			$stored = get_user_meta( $user_id, self::META_KEY, true );

			return self::is_uuid( $stored ) ? $stored : $fallback;
		}

		try {
			$customer->read_meta_data( true );
			self::adopt_legacy_multisite_user_uuid( $customer );
			$customer->read_meta_data( true );

			return self::ensure_uuid_without_user_lock( $customer, $opts );
		} finally {
			$wpdb->get_var( $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $lock_name ) );
		}
	}

	/**
	 * Legacy WP_Term adapter for the shared identity path (ADR 0021).
	 *
	 * @param mixed $term WP_Term-like object or numeric term id.
	 */
	public static function ensure_term_uuid( $term ): string {
		$term_id = \is_object( $term ) && isset( $term->term_id ) ? (int) $term->term_id : (int) $term;
		if ( $term_id <= 0 ) {
			return '';
		}

		$adapter = new Term_Meta_Adapter( $term_id );

		return self::ensure_uuid(
			$adapter,
			array( 'collides' => array( __CLASS__, 'uuid_owned_by_other_term' ) )
		);
	}

	/**
	 * Return a copy of the SERIALIZED payload whose `meta_data` mirrors `$uuid`
	 * exactly once (entries here are arrays: ['id'=>,'key'=>,'value'=>]). Drops
	 * blank / duplicate / mismatched `_woocommerce_pos_uuid` entries so the served
	 * record always carries one canonical identity.
	 */
	public static function ensure_in_payload( array $payload, string $uuid ): array {
		$meta   = ( isset( $payload['meta_data'] ) && \is_array( $payload['meta_data'] ) ) ? $payload['meta_data'] : array();
		$others = array();
		foreach ( $meta as $entry ) {
			$key = Meta_Entry::key( $entry );
			if ( self::META_KEY !== $key ) {
				$others[] = $entry;
			}
		}
		$others[]             = array(
			'key' => self::META_KEY,
			'value' => $uuid,
		);
		$payload['meta_data'] = array_values( $others );

		return $payload;
	}

	/**
	 * Hook for the per-collection `woocommerce_pos_sync_serialized_*` filters (product,
	 * order, …): stamp the served record's stable uuid (persisting a new one if
	 * needed) and mirror it into the payload, so EVERY read carries the identity the
	 * client keys on — regardless of how the record was born. A non-array payload or
	 * an object that can't carry meta passes through unchanged.
	 *
	 * Collection-agnostic: `ensure_uuid` only needs the WC_Data meta API
	 * (get/update/save_meta_data), which orders (HPOS-safe), customers, and terms all
	 * provide. Collision detection IS storage-specific: orders live in HPOS tables
	 * (not `wp_postmeta`), so they get the order-aware detector; products/variations
	 * keep the post-scoped one. Customers and terms use their storage-specific
	 * adapters and detectors.
	 *
	 * @param mixed      $payload
	 * @param mixed      $object
	 * @param null|mixed $request
	 */
	public static function stamp_serialized_record( $payload, $object, $request = null ) {
		if ( ! \is_array( $payload ) ) {
			return $payload;
		}
		$collides = is_a( $object, 'WC_Abstract_Order' )
			? array( __CLASS__, 'uuid_owned_by_other_order' )
			: array( __CLASS__, 'uuid_owned_by_other' );
		// Read path over a record loaded from its own row: a loaded, unchanged uuid
		// is trusted (ADR 0038). On the legacy CPT order store the detector is a
		// full `wp_postmeta` uuid walk per served order (#1805).
		$uuid = self::ensure_uuid(
			$object,
			array(
				'collides'        => $collides,
				'trust_persisted' => true,
			)
		);

		return '' === $uuid ? $payload : self::ensure_in_payload( $payload, $uuid );
	}

	/**
	 * Order-aware variant of {@see uuid_owned_by_other}. HPOS order meta does NOT live
	 * in `wp_postmeta`, so the post-scoped detector can't see an order that already owns
	 * `$uuid` — a duplicated/imported order with a copied uuid would slip through and two
	 * orders would share one RxDB key. Query the orders store (HPOS-safe via
	 * `wc_get_orders`) for the uuid; a match on a DIFFERENT order id is a real collision.
	 *
	 * @param mixed $uuid
	 * @param mixed $object
	 */
	public static function uuid_owned_by_other_order( $uuid, $object ): bool {
		if ( ! \is_object( $object ) || ! method_exists( $object, 'get_id' ) || ! \function_exists( 'wc_get_orders' ) ) {
			return false;
		}
		$order_id = (int) $object->get_id();
		foreach ( self::get_order_ids_by_uuid( (string) $uuid ) as $other_id ) {
			if ( (int) $other_id !== $order_id ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Return at most two order ids carrying a UUID. The legacy create controller
	 * treats two results as an ambiguous identity and fails closed.
	 *
	 * Datastore-aware direct meta lookup: under HPOS the uuid lives in
	 * `wc_orders_meta`, otherwise in `wp_postmeta`. `wc_get_orders()` with a
	 * `meta_query` is NOT supported on the CPT order datastore (it fires a
	 * `doing_it_wrong` and returns unfiltered results), so we query the meta table
	 * directly — the same shape the plugin's other order-uuid lookups use.
	 *
	 * DELIBERATELY UNORDERED — do not add an `ORDER BY` back (#1725). Every caller
	 * asks a counting question ("does a DIFFERENT record hold this uuid?", "is this
	 * uuid ambiguous?"), so WHICH two ids come back is immaterial. `wp_postmeta`
	 * indexes `meta_key` but never `meta_value`, and `ORDER BY m.post_id ASC LIMIT 2`
	 * made the optimizer abandon the `meta_key` index for an id-ordered walk that
	 * expects to stop early. In the common case the uuid matches at most one row, so
	 * it never reaches two and walks the whole table: 887,404 rows and ~1.0 s per
	 * call on a real store, versus ~51 ms without the clause. HPOS escapes it only
	 * because `wc_orders_meta` carries a composite `(meta_key, meta_value)` index —
	 * by data, not by code — so the clause is gone from both branches.
	 */
	public static function get_order_ids_by_uuid( string $uuid ): array {
		global $wpdb;
		if ( ! isset( $wpdb ) ) {
			return array();
		}

		$order_util = '\\Automattic\\WooCommerce\\Utilities\\OrderUtil';
		$hpos       = class_exists( $order_util )
			&& method_exists( $order_util, 'custom_orders_table_usage_is_enabled' )
			&& call_user_func( array( $order_util, 'custom_orders_table_usage_is_enabled' ) );

		if ( $hpos ) {
			$ids = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT DISTINCT m.order_id FROM {$wpdb->prefix}wc_orders_meta m"
					. " JOIN {$wpdb->prefix}wc_orders o ON o.id = m.order_id AND o.type = 'shop_order'"
					. ' WHERE m.meta_key = %s AND m.meta_value = %s'
					. " AND o.status NOT IN ('trash','auto-draft')"
					. ' LIMIT 2',
					self::META_KEY,
					$uuid
				)
			);
		} else {
			$ids = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT DISTINCT m.post_id FROM {$wpdb->postmeta} m"
					. " JOIN {$wpdb->posts} p ON p.ID = m.post_id AND p.post_type = 'shop_order'"
					. ' WHERE m.meta_key = %s AND m.meta_value = %s'
					. " AND p.post_status NOT IN ('trash','auto-draft')"
					. ' LIMIT 2',
					self::META_KEY,
					$uuid
				)
			);
		}

		return \is_array( $ids ) ? array_values( $ids ) : array();
	}

	/**
	 * Register WRITE-time stamping: a record gets its uuid the moment it is saved,
	 * so every read path (catalog proxy, change-signal hydration) then serves it
	 * straight from postmeta with no per-path stamping copy. Hooked BEFORE the data
	 * store writes, so the uuid lands in the SAME save — no second write, no
	 * change-log cascade, and no concurrent-first-READ race (stamping is per-save,
	 * not per-reader). The read-time filter remains as a fallback for records that
	 * predate these hooks until the backfill runs.
	 */
	public static function register_hooks(): void {
		if ( ! \function_exists( 'add_action' ) ) {
			return;
		}
		add_action( 'woocommerce_before_product_object_save', array( __CLASS__, 'stamp_on_save' ), 10, 1 );
		add_action( 'woocommerce_before_product_variation_object_save', array( __CLASS__, 'stamp_on_save' ), 10, 1 );
		// A record leaving the trash must re-prove ownership: it was invisible to
		// the detector while inactive, so another record may hold its uuid now.
		// Both storage lanes — `untrashed_post` never fires for HPOS orders and
		// `woocommerce_untrash_order` never fires for posts (ADR 0038).
		add_action( 'untrashed_post', array( __CLASS__, 'recheck_ownership_after_untrash' ), 10, 1 );
		add_action( 'woocommerce_untrash_order', array( __CLASS__, 'recheck_order_ownership_after_untrash' ), 10, 1 );
	}

	/**
	 * Before-save hook: ensure the WC object carries a unique uuid as part of the
	 * in-progress save (persist:false — the save itself writes it).
	 *
	 * The ownership scan runs only for a uuid that did NOT come from this record's
	 * own persisted meta row (`trust_persisted`, {@see is_own_persisted_uuid}). It
	 * walks every uuid row in `wp_postmeta` (no `meta_value` index), so on every
	 * save it cost 0.46 s and 30k rows examined on a 30k-product store, 114 times
	 * an hour, for a fact the loaded row already stated (#1805, ADR 0038).
	 *
	 * @param mixed $object
	 */
	public static function stamp_on_save( $object ): void {
		self::ensure_uuid(
			$object,
			array(
				'collides'        => array( __CLASS__, 'uuid_owned_by_other' ),
				'persist'         => false,
				'trust_persisted' => true,
			)
		);
	}

	/**
	 * Re-prove uuid ownership for a post that just left the trash (products,
	 * variations, and orders on the legacy CPT store).
	 *
	 * A trashed record is not a live owner, so a clone or import made while it
	 * was in the trash legitimately kept the copied uuid — and the tills now key
	 * on that record. A native restore (wp-admin's Restore, `wp_untrash_post()`)
	 * persists the status change before any WC object save, so neither the write
	 * hook nor the trusted read path ever sees a trash→live transition: this hook
	 * is the one seam. It runs the detector once per restore — a rare event — and
	 * re-keys the RESTORED record when another live record owns its uuid, never
	 * the record the tills already hold (ADR 0038).
	 *
	 * @param mixed $post_id Restored post id (`untrashed_post`).
	 */
	public static function recheck_ownership_after_untrash( $post_id ): void {
		$post_id   = (int) $post_id;
		$post_type = \function_exists( 'get_post_type' ) ? get_post_type( $post_id ) : '';
		if ( \in_array( $post_type, array( 'product', 'product_variation' ), true ) ) {
			$object   = \function_exists( 'wc_get_product' ) ? wc_get_product( $post_id ) : null;
			$collides = array( __CLASS__, 'uuid_owned_by_other' );
		} elseif ( 'shop_order' === $post_type ) {
			$object   = \function_exists( 'wc_get_order' ) ? wc_get_order( $post_id ) : null;
			$collides = array( __CLASS__, 'uuid_owned_by_other_order' );
		} else {
			return;
		}
		if ( \is_object( $object ) && method_exists( $object, 'get_id' ) && (int) $object->get_id() === $post_id ) {
			self::ensure_uuid( $object, array( 'collides' => $collides ) );
		}
	}

	/**
	 * HPOS twin of {@see recheck_ownership_after_untrash}: `untrashed_post` never
	 * fires for orders in the orders table, and `woocommerce_untrash_order` fires
	 * BEFORE the data store restores the status (a detector run there would see a
	 * still-trashed row and, worse, the restore's own save would write the old meta
	 * back). Arm a one-shot on the order's first live object save and re-prove
	 * ownership then — the same seam {@see Integrity_Digest::record_order_untrashed}
	 * uses.
	 *
	 * @param mixed $order_id Order being restored (`woocommerce_untrash_order`).
	 */
	public static function recheck_order_ownership_after_untrash( $order_id ): void {
		$order_id = (int) $order_id;
		$handler  = static function ( $order ) use ( $order_id, &$handler ): void {
			if ( ! \is_object( $order ) || ! method_exists( $order, 'get_id' ) || ! method_exists( $order, 'get_status' )
				|| (int) $order->get_id() !== $order_id || 'trash' === $order->get_status() ) {
				return;
			}
			remove_action( 'woocommerce_after_order_object_save', $handler );
			self::ensure_uuid( $order, array( 'collides' => array( __CLASS__, 'uuid_owned_by_other_order' ) ) );
		};
		add_action( 'woocommerce_after_order_object_save', $handler );
	}

	/**
	 * True when $entry — the record's canonical uuid entry — was READ from this
	 * record's own meta row and has not been changed in memory since: the uuid is
	 * already this record's persisted identity, not a value that arrived by copy.
	 *
	 * The ownership detector exists to catch a uuid that reached a record some
	 * other way, and every such provenance fails this test: a duplicated object
	 * (WooCommerce's "Duplicate" clones the meta with its ids cleared), an importer
	 * rewriting the value in memory (a tracked change on the entry), a record with
	 * no id yet, a record returning from the trash. What passes is the ordinary
	 * save or read — a stock change, a price edit, a REST update, a served record —
	 * where the detector re-proved a fact at a cost linear in catalog size.
	 *
	 * A copy made WITHOUT hooks (direct SQL, a migration tool) passes too, on both
	 * records: neither save re-keys it. Deliberate (ADR 0038): the detector caught
	 * that shape only when one of the two next saved, and re-keyed whichever that
	 * was — the original as readily as the copy. The collision backfill
	 * (`/uuid/backfill?mode=collisions`) walks the store once in bounded pages and
	 * re-keys the later copy, never the owner; it is the repair for that shape.
	 *
	 * Duck-typed on WC_Data / WC_Meta_Data (`get_id`, `get_changes`, `get_data`,
	 * `->id`): a bare array or a fake without change tracking is never trusted.
	 *
	 * @param mixed $object
	 * @param mixed $entry
	 */
	private static function is_own_persisted_uuid( $object, $entry ): bool {
		if ( ! \is_object( $object ) || ! method_exists( $object, 'get_id' ) || (int) $object->get_id() <= 0 ) {
			return false;
		}
		// A record coming back from trash/auto-draft was invisible to the ownership
		// scan while inactive (inactive rows are not live owners), so another record
		// may have adopted its uuid in the meantime — and that record is what the
		// tills now key on. The transition back to live is the one ordinary save
		// that must re-prove ownership; the loaded status is still in get_data()
		// because the before-save hook fires ahead of apply_changes().
		if ( method_exists( $object, 'get_changes' ) && method_exists( $object, 'get_data' ) ) {
			$changes = (array) $object->get_changes();
			if ( isset( $changes['status'] ) ) {
				$loaded = (array) $object->get_data();
				if ( \in_array( (string) ( $loaded['status'] ?? '' ), array( 'trash', 'auto-draft' ), true ) ) {
					return false;
				}
			}
		}
		// Only the canonical entry's provenance decides; a trailing duplicate is
		// pruned by the save either way.
		return \is_object( $entry )
			&& method_exists( $entry, 'get_changes' )
			&& ! empty( $entry->id )
			&& array() === $entry->get_changes();
	}

	/**
	 * True when $uuid is already stored as `_woocommerce_pos_uuid` on a DIFFERENT
	 * post (a cloned/imported record that copied the meta). Post-scoped (products
	 * + variations); terms/customers live in their own meta tables and get their
	 * own detector when those seams land. Returns false when $wpdb or the object's
	 * id is unavailable (e.g. unit tests inject a fake detector instead).
	 *
	 * @param mixed $uuid
	 * @param mixed $object
	 */
	public static function uuid_owned_by_other( $uuid, $object ): bool {
		global $wpdb;
		if ( ! isset( $wpdb ) || ! \is_object( $object ) || ! method_exists( $object, 'get_id' ) ) {
			return false;
		}
		// Only an ACTIVE post counts as a live owner — a trashed/auto-draft record
		// sharing the uuid is not a real collision (it will never be served), so it
		// must not force a needless regeneration on the active record.
		$sql = $wpdb->prepare(
			"SELECT COUNT(*) FROM {$wpdb->postmeta} m
             JOIN {$wpdb->posts} p ON p.ID = m.post_id
             WHERE m.meta_key = %s AND m.meta_value = %s AND m.post_id <> %d
               AND p.post_status NOT IN ('trash','auto-draft')",
			self::META_KEY,
			$uuid,
			(int) $object->get_id()
		);

		return (int) $wpdb->get_var( $sql ) > 0;
	}

	/**
	 * User-table twin of uuid_owned_by_other for CUSTOMERS (WC_Customer over
	 * wp_usermeta). DELIBERATE asymmetry: WP users have no post_status / trash, so
	 * every user row carrying the uuid is a live owner — there is NO status
	 * exclusion (a status filter here would reference a non-existent column).
	 *
	 * @param mixed $uuid
	 * @param mixed $object
	 */
	public static function uuid_owned_by_other_user( $uuid, $object ): bool {
		global $wpdb;
		if ( ! isset( $wpdb ) || ! \is_object( $object ) || ! method_exists( $object, 'get_id' ) ) {
			return false;
		}
		$sql = $wpdb->prepare(
			"SELECT COUNT(*) FROM {$wpdb->usermeta} m
             JOIN {$wpdb->users} u ON u.ID = m.user_id
             WHERE m.meta_key = %s AND m.meta_value = %s AND m.user_id <> %d",
			self::META_KEY,
			$uuid,
			(int) $object->get_id()
		);

		return (int) $wpdb->get_var( $sql ) > 0;
	}

	/**
	 * Adoption-gate twin of uuid_owned_by_other_user(): also checks the CURRENT
	 * blog's legacy per-blog key (`_woocommerce_pos_uuid_{blog}`), because a
	 * value another user still holds under that legacy key is the same RxDB
	 * primary key, and adopting it would fork that user's identity (#1465).
	 *
	 * Deliberately NOT used for the live-identity collision check: a stale
	 * duplicated legacy row must never discard an already-served network uuid,
	 * so `collides` stays scoped to the network key.
	 *
	 * @param mixed       $uuid     Candidate legacy uuid.
	 * @param WC_Customer $customer Customer being stamped.
	 */
	private static function legacy_uuid_owned_by_other_user( $uuid, WC_Customer $customer ): bool {
		global $wpdb;
		if ( self::uuid_owned_by_other_user( $uuid, $customer ) ) {
			return true;
		}
		if ( ! isset( $wpdb ) ) {
			return false;
		}
		$sql = $wpdb->prepare(
			"SELECT COUNT(*) FROM {$wpdb->usermeta} m
             JOIN {$wpdb->users} u ON u.ID = m.user_id
             WHERE m.meta_key = %s AND m.meta_value = %s AND m.user_id <> %d",
			self::META_KEY . '_' . get_current_blog_id(),
			$uuid,
			(int) $customer->get_id()
		);

		return (int) $wpdb->get_var( $sql ) > 0;
	}

	/**
	 * Term-table twin for CATEGORIES + BRANDS. CROSS-TAXONOMY by design: product_cat
	 * and product_brand SHARE wp_termmeta, so a uuid on a different term in EITHER
	 * taxonomy is a real RxDB-primary-key clash — match on term_id across ALL
	 * taxonomies (NO taxonomy scoping). Terms have no trash/status, so no status
	 * exclusion (like users, unlike posts).
	 *
	 * @param mixed $uuid
	 * @param mixed $object
	 */
	public static function uuid_owned_by_other_term( $uuid, $object ): bool {
		global $wpdb;
		if ( ! isset( $wpdb ) || ! \is_object( $object ) || ! method_exists( $object, 'get_id' ) ) {
			return false;
		}
		$sql = $wpdb->prepare(
			"SELECT COUNT(*) FROM {$wpdb->termmeta} WHERE meta_key = %s AND meta_value = %s AND term_id <> %d",
			self::META_KEY,
			$uuid,
			(int) $object->get_id()
		);

		return (int) $wpdb->get_var( $sql ) > 0;
	}

	/**
	 * A v4 uuid — `wp_generate_uuid4()` under WordPress, else a local fallback.
	 */
	public static function generate_uuid(): string {
		if ( \function_exists( 'wp_generate_uuid4' ) ) {
			return wp_generate_uuid4();
		}
		$data    = random_bytes( 16 );
		$data[6] = \chr( ( \ord( $data[6] ) & 0x0f ) | 0x40 ); // version 4
		$data[8] = \chr( ( \ord( $data[8] ) & 0x3f ) | 0x80 ); // variant 10xx

		return vsprintf( '%s%s-%s-%s-%s-%s%s%s', str_split( bin2hex( $data ), 4 ) );
	}

	/**
	 * Collapse duplicate `_woocommerce_pos_uuid` metas to ONE canonical entry —
	 * keep the first valid uuid, delete every other uuid meta (a concurrent-stamp
	 * duplicate, or a blank/invalid straggler). Only persisted metas (with a meta
	 * id) are deleted; a freshly-added unsaved one is left for the in-progress save.
	 * When $persist, the cleanup is saved immediately; otherwise the ongoing save
	 * (before-save hook) applies the deletions. Mirrors production's dedup, and is
	 * what makes the concurrent-first-stamp outcome correct without a lock.
	 *
	 * @param mixed $object
	 */
	private static function prune_duplicate_uuid_meta( $object, bool $persist ): void {
		if ( ! \is_object( $object ) || ! method_exists( $object, 'get_meta_data' ) || ! method_exists( $object, 'delete_meta_data_by_mid' ) ) {
			return;
		}
		$kept_valid = false;
		$deleted    = false;
		foreach ( (array) $object->get_meta_data() as $meta ) {
			if ( ! \is_object( $meta ) || self::META_KEY !== Meta_Entry::key( $meta ) ) {
				continue;
			}
			if ( ! $kept_valid && self::is_uuid( Meta_Entry::value( $meta ) ) ) {
				$kept_valid = true; // keep the first valid uuid meta

				continue;
			}
			$mid = $meta->id ?? null;
			if ( null !== $mid ) {
				$object->delete_meta_data_by_mid( $mid );
				$deleted = true;
			}
		}
		if ( $deleted && $persist && method_exists( $object, 'save_meta_data' ) ) {
			$object->save_meta_data();
		}
	}
}

```
