# CONTEXT.md — 2meet Data Optimizer Domain Glossary This file defines the canonical vocabulary for 2meet Data Optimizer. Architecture reviews, AI assistance, and code documentation must use these terms exactly. > **Status (2026-07-31, v1.0.0):** 核心 451 unit / 416 integration · hivepress-addon 145 unit / 57 integration · woocommerce-addon 10 unit / 18 integration · PHPCS 0 errors · PHPStan L6(baseline 710)· gitea CI 6 job 全綠。 > v1.0.0 取代 `wp-data-optimizer` v3.4.6,該外掛已退休(本機目錄改名 `.retired`,git 歷史留在 gitea)。版號自 1.0.0 重啟。 > **本外掛只有通用引擎**:4 entity(post / user / term / comment)反 EAV、zone 表、migration、wizard、admin、CLI、REST、snapshot、diagnostic、notifications。HivePress / WooCommerce / LatePoint / 2meet-* 整合層全部在 11 個獨立 AddOn,核心不得引用它們的類別。 > **共用命名空間刻意保留**:`wp_wpdo_*` 表、`wpdo_` option / cron / hook 前綴、`wpdo/v1` REST namespace 一律不改名 —— 這是資料層零遷移的前提。類別與函式前綴才是 `TMDO_` / `tmdo_`,並以 `class_alias()` 對外保留 `WPDO_*`。 > HivePress addon detection uses the **WP active-plugins list** as the authoritative signal (addons publish no per-addon class/const; they register via `add_filter('hivepress/v1/extensions', …)`). See `TMDO_HivePress_Detector::detect()` / `active_plugin_files()` in the HivePress AddOn. > **測試 harness 隔離**:`TMDO_FSM_GUARD_DISABLED`(integration bootstrap,讓測試可強制 module state)、`TMDO_Routing_Predicate::flush_cache()`、`TMDO_Schema_Manager::flush_table_exists_cache()` 三者是跨測試污染的解法,**不是** production 缺陷的補丁。 > **gitea runner 為 wpdev 全 repo 共享**(host-mode label `ubuntu-latest:host`,無 service container,integration job 直接連本機 MariaDB)。CI 卡 pending 時先查 `GET /api/v1/user/actions/runners`。 --- ## Zone A dedicated storage tier for WordPress postmeta, optimised for a specific access pattern. | Zone | Slug | Table pattern | Purpose | |------|------|---------------|---------| | Hot | `hot` | `wpdo_hot_{post_type}` | Flat columns for search/filter (index-friendly) | | Warm | `warm` | `wpdo_warm` | TTL key-value store for counts and transient flags | | Cold | `cold` | `wpdo_cold_{post_type}` | JSON blob for display-only fields | | Archive | `archive` | `wpdo_archive` | gzip-compressed historical data | A post type's fields are assigned to exactly one zone via the **Schema Registry**. --- ## Schema Registry `TMDO_Schema_Registry` — singleton that maps `(post_type, meta_key) → field definition`. A **field definition** carries `zone`, `column` (flat name), and optional `type`. The registry is populated at `wpdo_register_fields` action by integrations and adapters. --- ## Zone Router `TMDO_Zone_Router` — static dispatch layer introduced in v3.0.1. Single module that knows how to route `read`, `write`, and `delete_field` to the correct zone handler (Hot / Warm / Cold / Archive) given a field definition. Also produces canonical **module names** for Feature Flag lookups. > **Why it exists**: before v3.0.1, routing logic was duplicated across `TMDO_Sync_Bridge`, > `TMDO_REST_API`, and two query files. Extracting it here creates one seam for tests and > one place to change zone routing decisions. Interface (all static): ``` TMDO_Zone_Router::module_name(zone, post_type) → string TMDO_Zone_Router::read(field, post_id, post_type, meta_key) → mixed TMDO_Zone_Router::write(field, post_id, post_type, meta_key, value) → void TMDO_Zone_Router::delete_field(field, post_id, post_type, meta_key) → void TMDO_Zone_Router::delete_post(post_id, post_type) → void # v3.4.0: all-zone post cleanup ``` --- ## Routing Predicate `TMDO_Routing_Predicate` — centralised predicate module introduced in v3.4.0. Two recurring guard patterns previously scattered across `TMDO_Sync_Bridge`, `TMDO_Query_Router`, and the REST API are now a single module: ``` TMDO_Routing_Predicate::entity_bridge_owns(meta_key) → bool TMDO_Routing_Predicate::should_write_to_zone(post_type, zone) → bool TMDO_Routing_Predicate::should_read_from_zone(post_type, zone) → bool TMDO_Routing_Predicate::should_query_from_zone(post_type, zone) → bool TMDO_Routing_Predicate::flush_cache() → void # test isolation helper ``` `entity_bridge_owns()` returns true when `TMDO_Mode_Manager::writes_to_flat('post')` AND the key is registered in `TMDO_Entity_Registry` for 'post'. Result is memoised in a request-level static cache (`$entity_bridge_cache`) keyed by `meta_key`. The three `should_*` predicates are thin compositions of `TMDO_Zone_Router::module_name()` + the matching `TMDO_Feature_Flags::is_*()` method. > **Why it exists**: without this module, callers re-implemented the same two-step > "module_name then Feature_Flags" pattern inline. The **deletion test** confirms depth: > removing it pushes the repeated guard back into 8+ call sites in Sync_Bridge alone. --- ## Sync Bridge `TMDO_Sync_Bridge` — zone-aware dual-write dispatcher hooked into the WordPress metadata API. Intercepts `get/update/add/delete_post_metadata` and `before_delete_post`. Calls `TMDO_Zone_Router::read/write/delete_field/delete_post` directly — no private I/O wrappers (v3.4.1: delegate methods inlined and removed). Uses the **Routing Predicate** for all ownership and activation checks. The bridge skips fields owned by the **Entity Bridge** to avoid duplicate writes. Its only private helper is `get_field_cached()` — a request-level Schema Registry lookup cache keyed by `post_type:meta_key`. --- ## Module A named unit whose lifecycle is tracked by the **Feature Flags** 7-state machine. Module names follow the convention: `hot_{post_type}`, `cold_{post_type}`, `warm`, `archive`. `TMDO_Zone_Router::module_name()` is the single authoritative source for this naming. --- ## Feature Flags / 7-state FSM `TMDO_Feature_Flags` — state machine governing migration lifecycle for each module. States: `idle → dual_write → backfill → verify → cutover → cleanup → complete` `is_write_active(module)` returns true for `dual_write` and above. `is_read_custom(module)` returns true for `cutover` and above. --- ## Entity Bridge Newer anti-EAV system (v2.5+) covering user, term, comment, and post entities via a **Hook Bus** that intercepts native `*meta()` API calls and writes to flat entity tables. `TMDO_Mode_Manager` controls per-entity mode: `disabled → dual_write → shadow_read → aeav_only`. `TMDO_Entity_Registry` maps `(entity_type, group) → fields`. The Entity Bridge and the Zone system coexist; the Sync Bridge's `is_owned_by_entity_bridge()` guard prevents duplicate writes. --- ## Migration Phase (Strategy pattern) Interface: `TMDO_Migration_Phase_Interface` (v3.0.1). Each phase encapsulates a single step of the Entity Bridge migration pipeline: | Phase class | Slug | What it does | |-------------|------|--------------| | `TMDO_Phase_Diagnose` | `diagnose` | Records preflight EAV row count / ratio | | `TMDO_Phase_Backup` | `backup` | Dumps native meta table to `uploads/wpdo-backups/` | | `TMDO_Phase_Demote` | `demote` | aeav_only → dual_write (rollback entry point) | | `TMDO_Phase_Install_Schema` | `install_schema` | Creates flat tables via Schema Manager | | `TMDO_Phase_Backfill_Bulk` | `backfill_bulk` | Pivots text-only groups via INSERT…SELECT | | `TMDO_Phase_Backfill_Unserialize` | `backfill_unserialize` | Row-by-row migration for JSON fields | | `TMDO_Phase_Promote_Shadow` | `promote_shadow` | dual_write → shadow_read | | `TMDO_Phase_Verify_Sample` | `verify_sample` | Samples entities, compares flat vs EAV | | `TMDO_Phase_Promote_Aeav` | `promote_aeav` | shadow_read → aeav_only | | `TMDO_Phase_Cleanup` | `cleanup` | Deletes managed keys from EAV table | | `TMDO_Phase_Completed` | `completed` | Terminal — marks job done, returns `'done'` | `TMDO_Migration_Phase_Base` provides shared helpers (`log()`, `get_managed_keys()`, `execute_bulk_pivot()`, `values_loose_equal()`). The **Migration Orchestrator** (`TMDO_Migration_Orchestrator`) injects phase objects and calls `execute($job)` in sequence, advancing through the pipeline. > **Seam**: the interface is the test surface. A phase can be tested by constructing it > with a stub entity type, calling `execute()` with a job array, and asserting on the > job's `state`, `log`, and `metrics` — no hooks or DB needed for unit tests. --- ## Standard Post Interceptor (Template Method pattern) `TMDO_Standard_Post_Interceptor` — abstract base (v3.0.1) for HPCT-inherited interceptors. Eliminates the three hook methods (`filter_update_meta`, `action_insert_post`, `action_delete_post`) that were previously duplicated across four interceptor classes. Subclasses declare only: ```php public const FIELD_MAP = ['meta_key' => 'flat_column', ...]; protected function get_post_type(): string { ... } protected function get_table_key(): string { ... } protected function build_insert_data(int $post_id, WP_Post $post, string $now): array { ... } ``` Concrete subclasses: `TMDO_Reviews_Interceptor`, `TMDO_Messages_Interceptor`, `TMDO_Memberships_Interceptor`, `TMDO_Requests_Interceptor`. --- ## External Partners `TMDO_Core::EXTERNAL_PARTNERS` (v3.0.1) — PHP class-name constant array listing all external plugin integrations that self-register via the Hook Bus. ```php ['TMDO_Infocards', 'TMDO_Bookings', 'TMDO_Quotation', 'TMDO_Mobile_Bridge', 'TMDO_Collab', 'TMDO_Playlist'] ``` Used in the late-bind priority-30 closure in `2meet-data-optimizer.php` to initialize partner integrations only when their plugin class is present. Single source of truth — previously the list existed only inside the closure and diverged from `TMDO_Core`. --- ## HPCT Interceptors Manifest > **v1.0.0:本節描述的是 AddOn 的內部結構,核心已無此常數。** > `HPCT_INTERCEPTORS` 與 `register_hpct_interceptors()` 隨整合層一起搬到 > `2meet-data-optimizer-hivepress-addon` 的 bootstrap;核心刻意不保留,否則會引用 > 8 個核心不存在的類別名。下表列的 interceptor ↔ query-handler 配對關係仍然成立。 `TMDO_Core::HPCT_INTERCEPTORS` (v3.4.0) — PHP class-constant array that is the single authoritative list of HPCT-inherited interceptor → query-handler pairs: ```php TMDO_Reviews_Interceptor::class → TMDO_Reviews_Query::class TMDO_Messages_Interceptor::class → TMDO_Messages_Query::class TMDO_Favorites_Interceptor::class → null TMDO_Memberships_Interceptor::class → TMDO_Memberships_Query::class TMDO_Statistics_Interceptor::class → null TMDO_Requests_Interceptor::class → TMDO_Requests_Query::class TMDO_Listing_Meta_Interceptor::class → TMDO_Listing_Meta_Query::class TMDO_LatePoint_Interceptor::class → null ``` `register_hpct_interceptors()` iterates this constant; adding a new interceptor requires only one entry here — no code change in the registration method. --- ## HPCT (HP Custom Tables) The plugin this replaces. Still referenced in import path (`wp tmdo import-hpct`). Any "HPCT-inherited" interceptor means it originated in HPCT and was migrated here. --- ## Adapter (HivePress) > **v1.0.0:這 13 個 adapter 住在 `2meet-data-optimizer-hivepress-addon`,不在核心。** > 核心只提供 `TMDO_Schema_Registry` 與 `wpdo_register_fields` 這個 seam。 Each HivePress addon (core, bookings, events, …) has a corresponding `TMDO_Hivepress_*_Adapter` that registers its meta keys into the Schema Registry at `wpdo_register_fields`. The adapter is the seam between HivePress and the zone system. --- ## Anti-EAV The overarching goal: eliminate Entity-Attribute-Value (EAV) reads from `wp_postmeta`, `wp_usermeta`, etc. by moving data to flat tables. Compliance is enforced by `wpdo-policy-enforcer.php` (mu-plugin) and the `wp tmdo lint` CLI gate. --- ## Entity Group A named set of fields registered under one entity type (user / post / term / comment), stored as one flat table: `wp_wpdo_{entity_type}_{group_name}`. Each group has **one row per entity** (keyed on `user_id` / `post_id` / etc.). Fields within the group become typed columns; `sanitize_column_name()` produces the column name (strips all non-`[a-zA-Z0-9_]` characters — hyphens are removed, not replaced). Group registration: `TMDO_Entity_Registry::register_group(entity_type, group_name, fields[])`. Canonical user groups (v3.1.4): | Group | Table | Purpose | |-------|-------|---------| | `membership` | `wp_wpdo_user_membership` | Tier, points, expiry | | `activity` | `wp_wpdo_user_activity` | Login counters, last-active | | `profile` | `wp_wpdo_user_profile` | Bio, avatar, display name | | `sso` | `wp_wpdo_user_sso` | Hub token cache, SLO hash | | `core_profile` | `wp_wpdo_user_core_profile` | WP first_name, last_name, nickname, description | | `social` | `wp_wpdo_user_social` | 15 social platform URLs | | `commerce` | `wp_wpdo_user_commerce` | WC billing/shipping + runtime stats (wc_last_active, wc_order_count_wp, last_update) | | `hp_user` | `wp_wpdo_user_hp_user` | HP favorites, avatar, hp_verified | | `admin_prefs` | `wp_wpdo_user_admin_prefs` | WP default admin-UI keys written by wp_insert_user + extended UI prefs (22 keys total, v3.1.4) | --- ## EAV Floor The minimum irreducible rows that must remain in the native meta table even after full Entity Bridge migration to `aeav_only`. For `wp_usermeta`, the floor consists of: | Key | Rows per user | Why irreducible | |-----|---------------|-----------------| | `wp_capabilities` | 1 | WP core reads directly in `WP_User` constructor (not through `get_user_meta`) | | `session_tokens` | 0–N | WP authentication reads sessions directly; Hook Bus cannot intercept session creation safely | | `_application_passwords` | 0–1 | WP reads directly during REST auth | **Theoretical minimum ratio** for a standard WordPress site: `(users + sessions + app_passwords) / users ≈ 1:1.07` (varies by active sessions). Any ratio above 1:1.07 represents reducible EAV that WPDO can absorb. --- ## WP Admin Prefs The set of `wp_usermeta` keys that WordPress core writes automatically: **Written by `wp_insert_user()` for every new user (7 keys)**: `rich_editing`, `syntax_highlighting`, `comment_shortcuts`, `admin_color`, `use_ssl`, `show_admin_bar_front`, `dismissed_wp_pointers` **Written on first admin-page visit or explicit user action (extended, registered v3.1.4)**: `show_welcome_panel`, `wp_persisted_preferences`, `nav_menu_recently_edited`, `wp_dashboard_quick_press_last_post_id`, `edit_*_per_page` variants, `community-events-location`, `wp_user-settings`, `wp_user-settings-time`, `managenav-menuscolumnshidden`, `metaboxhidden_nav-menus`, `dismissed_no_secure_connection_notice`, `meta-box-order_product` All 22 keys are registered in the `admin_prefs` Entity Group and routed to `wp_wpdo_user_admin_prefs` by the Hook Bus when user entity mode ≥ `dual_write`.