array( 'object_type' => 'product', 'identity' => array( 'id_type' => 'post', 'post_type' => 'product', 'detector' => 'uuid_owned_by_other', 'bulk_reader' => 'bulk_read_post_uuids', 'loader' => 'product', ), 'proxy' => array( 'route' => '/products', 'wc_route' => '/wc/v3/products', 'slug' => 'products', 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Products_Proxy_Behavior::class, ), 'write' => array( 'route' => '/wc/v3/products' ), 'journal' => array( 'object_type' => 'product' ), 'digest' => array( 'id_space' => 'products', 'label' => '', 'object_types' => array( 'product', 'variation' ), 'child_type' => 'variation', 'select' => 'row_digest_select_sql', 'id_column' => 'p.ID', 'live_max' => 'product_live_max_id_sql', 'servable' => 'servable_product_ids', 'published_ids' => 'published_product_ids', 'live_rows' => 'live_row_exists_sql', ), 'repair' => array( 'drill_down' => true, 'self_heal' => true, 'reason' => 'Products support drill-down and automatic repair, including variations in their shared id-space.', ), 'fingerprint' => array( 'barcode' => true ), 'backfill' => array( 'kind' => 'post', 'scan_post_types' => array( 'product', 'product_variation' ), ), ), 'variations' => array( 'object_type' => 'variation', 'identity' => array( 'id_type' => 'post', 'post_type' => 'product_variation', 'detector' => 'uuid_owned_by_other', // No bulk reader: variations are stamped through the // serialized-product filter, not a proxy list page. 'bulk_reader' => null, 'loader' => 'product', ), // No wcpos proxy lane, and that is principled (ADR 0034): the flat // /variations route is both the per-id hydration lane AND the // list/seed lane (bare collection pages for the idle trickle). 'proxy' => null, // Variations use WooCommerce's nested REST resource. The write controller // takes the parent from create payloads and the stored object thereafter. 'write' => array( 'route' => '/wc/v3/products' ), 'journal' => array( 'object_type' => 'variation' ), 'digest' => null, // folded into the products id-space (owner row carries it) 'repair' => array( 'drill_down' => null, 'self_heal' => null, 'reason' => 'Variations are repaired through the products id-space.', ), 'fingerprint' => array( 'barcode' => true ), 'backfill' => array( 'kind' => 'post', 'scan_post_types' => array( 'product', 'product_variation' ), ), ), 'orders' => array( 'object_type' => 'order', 'identity' => array( 'id_type' => 'order', 'detector' => 'uuid_owned_by_other_order', 'bulk_reader' => null, // payload mode — uuid read from served meta_data 'loader' => 'order', ), 'proxy' => array( 'route' => '/orders', 'wc_route' => '/wc/v3/orders', 'slug' => 'orders', 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Orders_Proxy_Behavior::class, ), 'write' => array( 'route' => '/wc/v3/orders' ), 'journal' => array( 'object_type' => 'order' ), // orders consume the journal via the payload-windowed pull lane, catalogue via the pointer stream 'digest' => array( 'id_space' => 'orders', 'label' => 'order ', 'object_types' => array( 'order' ), 'select' => 'order_digest_select_sql', 'id_column' => '{id}', 'live_max' => 'order_live_max_id_sql', 'live_rows' => 'order_live_row_exists_sql', ), 'repair' => array( 'drill_down' => null, 'self_heal' => null, 'reason' => 'Detection is available; drill-down and automatic repair are not implemented.', ), 'fingerprint' => array( 'barcode' => false ), 'backfill' => array( 'kind' => 'order' ), ), 'customers' => array( 'object_type' => 'customer', 'identity' => array( 'id_type' => 'user', 'detector' => 'uuid_owned_by_other_user', 'bulk_reader' => 'bulk_read_user_uuids', 'loader' => 'customer', ), 'proxy' => array( 'route' => '/customers', 'wc_route' => '/wc/v3/customers', 'slug' => 'customers', 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Customers_Proxy_Behavior::class, ), 'write' => array( 'route' => '/wc/v3/customers' ), 'journal' => array( 'object_type' => 'customer' ), 'digest' => array( 'id_space' => 'customers', 'label' => 'customer ', 'object_types' => array( 'customer' ), 'select' => 'customer_digest_select_sql', 'id_column' => 'u.ID', 'live_max' => 'customer_live_max_id_sql', 'live_rows' => 'customer_live_row_exists_sql', ), 'repair' => array( 'drill_down' => null, 'self_heal' => null, 'reason' => 'Detection is available; drill-down and automatic repair are not implemented.', ), 'fingerprint' => array( 'barcode' => false ), 'backfill' => array( 'kind' => 'user' ), ), 'categories' => array( 'object_type' => 'category', 'identity' => array( 'id_type' => 'term', 'taxonomy' => 'product_cat', 'detector' => 'uuid_owned_by_other_term', 'bulk_reader' => 'bulk_read_term_uuids', 'loader' => 'term', ), 'proxy' => array( 'route' => '/products/categories', 'wc_route' => '/wc/v3/products/categories', 'slug' => 'categories', 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Terms_Proxy_Behavior::class, ), 'write' => array( 'route' => '/wc/v3/products/categories' ), 'journal' => array( 'object_type' => 'category' ), 'digest' => null, 'repair' => array( 'drill_down' => null, 'self_heal' => null, 'reason' => 'No digest id-space is implemented for this collection.', ), 'fingerprint' => array( 'barcode' => false ), 'backfill' => array( 'kind' => 'term', 'taxonomy' => 'product_cat', ), ), 'brands' => array( 'object_type' => 'brand', 'identity' => array( 'id_type' => 'term', 'taxonomy' => 'product_brand', 'detector' => 'uuid_owned_by_other_term', 'bulk_reader' => 'bulk_read_term_uuids', 'loader' => 'term', ), 'proxy' => array( 'route' => '/products/brands', 'wc_route' => '/wc/v3/products/brands', 'slug' => 'brands', 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Terms_Proxy_Behavior::class, ), 'write' => array( 'route' => '/wc/v3/products/brands' ), 'journal' => array( 'object_type' => 'brand' ), 'digest' => null, 'repair' => array( 'drill_down' => null, 'self_heal' => null, 'reason' => 'No digest id-space is implemented for this collection.', ), 'fingerprint' => array( 'barcode' => false ), 'backfill' => array( 'kind' => 'term', 'taxonomy' => 'product_brand', ), ), 'tags' => array( 'object_type' => 'tag', 'identity' => array( 'id_type' => 'term', 'taxonomy' => 'product_tag', 'detector' => 'uuid_owned_by_other_term', 'bulk_reader' => 'bulk_read_term_uuids', 'loader' => 'term', ), 'proxy' => array( 'route' => '/products/tags', 'wc_route' => '/wc/v3/products/tags', 'slug' => 'tags', 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Terms_Proxy_Behavior::class, ), 'write' => null, // read-only: no client push path exists 'journal' => array( 'object_type' => 'tag' ), 'digest' => null, 'repair' => array( 'drill_down' => null, 'self_heal' => null, 'reason' => 'No digest id-space is implemented for this collection.', ), 'fingerprint' => array( 'barcode' => false ), 'backfill' => array( 'kind' => 'term', 'taxonomy' => 'product_tag', ), ), 'coupons' => array( 'object_type' => 'coupon', 'identity' => array( 'id_type' => 'post', 'post_type' => 'shop_coupon', 'detector' => 'uuid_owned_by_other', 'bulk_reader' => 'bulk_read_post_uuids', 'loader' => 'coupon', ), 'proxy' => array( 'route' => '/coupons', 'wc_route' => '/wc/v3/coupons', 'slug' => 'coupons', 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Coupons_Proxy_Behavior::class, ), 'write' => array( 'route' => '/wc/v3/coupons' ), 'journal' => array( 'object_type' => 'coupon' ), 'digest' => null, 'repair' => array( 'drill_down' => null, 'self_heal' => null, 'reason' => 'No digest id-space is implemented for this collection.', ), 'fingerprint' => array( 'barcode' => false ), 'backfill' => array( 'kind' => 'post', 'scan_post_types' => array( 'shop_coupon' ), ), ), 'tax_rates' => array( 'object_type' => 'tax_rate', 'identity' => null, // ADR 0009: keyed by WooCommerce id — no uuid identity, principled 'proxy' => array( 'route' => '/taxes', 'wc_route' => '/wc/v3/taxes', 'slug' => 'taxes', 'behavior' => \WCPOS\WooCommercePOS\API\V2\Proxy\Taxes_Proxy_Behavior::class, ), 'write' => null, // principled read-only 'journal' => array( 'object_type' => 'tax_rate' ), 'digest' => null, 'repair' => array( 'drill_down' => null, 'self_heal' => null, 'reason' => 'No digest id-space is implemented for this collection.', ), 'fingerprint' => array( 'barcode' => false ), 'backfill' => null, // no meta store to stamp ), ); /** Row lookup by canonical plural name. Null for unknown — callers decide * their unsupported behavior EXPLICITLY (no default-to-products, ever). */ public static function row( string $collection ): ?array { return self::ROWS[ $collection ] ?? null; } /** Inverse lookup: singular change-log object_type → row (+ its plural name * under '_collection'). `product` resolves EXPLICITLY — it is not a * fall-through default anywhere in this class. */ public static function by_object_type( string $object_type ): ?array { foreach ( self::ROWS as $collection => $row ) { if ( $row['object_type'] === $object_type ) { return array( '_collection' => $collection ) + $row; } } return null; } /** Resolve a singular object type to its canonical collection name. */ public static function collection_for_object_type( string $object_type ): ?string { $row = self::by_object_type( $object_type ); return null === $row ? null : $row['_collection']; } /** Inverse lookup: proxy resource slug → row (tax_rates' slug is `taxes`). */ public static function by_proxy_slug( string $slug ): ?array { foreach ( self::ROWS as $collection => $row ) { if ( isset( $row['proxy'] ) && $row['proxy']['slug'] === $slug ) { return array( '_collection' => $collection ) + $row; } } return null; } /** * Capability projection: every row carrying a non-null `$capability` * group, keyed by plural name. The write map, the proxy RESOURCES, the * digest dispatch and plugin.php's stamper wiring are all projections of * this — adding a collection means adding ONE row above. */ public static function with( string $capability ): array { $rows = array(); foreach ( self::ROWS as $collection => $row ) { if ( isset( $row[ $capability ] ) ) { $rows[ $collection ] = $row; } } return $rows; } /** The full canonical name list (tests, docs, admin surfaces). */ public static function names(): array { return array_keys( self::ROWS ); } }