Files
2meet-data-optimizer/CONTEXT.md
T
wpdev b63ab46f54
Tests / Integration Tests (push) Successful in 1m11s
Tests / Unit Tests (push) Failing after 11m56s
Anti-EAV Lint + Quality Gate / anti-eav-lint (push) Failing after 12m7s
Tests / PHPStan (push) Failing after 14m37s
Tests / PHPCS (push) Failing after 14m46s
Tests / PHP Lint (push) Failing after 14m57s
docs: 移植 readme.txt / CONTEXT.md / docs(backport A v3.4.6)
- readme.txt(WP 外掛目錄格式,隨 ZIP 發佈):Stable tag 對齊 1.0.0,
  changelog 補 1.0.0 條目
- CONTEXT.md(領域詞彙表):Status 區塊改寫為 v1.0.0 實況;
  HPCT_INTERCEPTORS 與 HivePress Adapter 兩節標註「已搬到 AddOn,核心無此常數」
- docs/:ENTITY_ADAPTER_COOKBOOK、2 篇 ADR、INTEGRATION_PATTERN_DECISION、
  anti-eav-lint.yml.template
  - cookbook 修掉兩個死連結(ANTI_EAV_PLAYBOOK 在來源外掛就不存在)
  - INTEGRATION_PATTERN_DECISION 加 v1.0.0 後記:結論已被 AddOn 拆分取代
  - template 改 wpdev/2meet-data-optimizer + ref v1.0.0 + wp tmdo lint
- README.md 文件索引補上以上 7 個檔案

前綴改寫刻意只動類別/函式/slug(WPDO_→TMDO_、wp-data-optimizer→2meet-...),
wpdo_ option/cron/hook/表名與 wpdo/v1 REST namespace 一律保留 —— 這是資料層
零遷移的前提。
2026-07-31 10:06:03 +08:00

15 KiB
Raw Blame History

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 L6baseline 710)· gitea CI 6 job 全綠。 v1.0.0 取代 wp-data-optimizer v3.4.6,該外掛已退休(本機目錄改名 .retired,git 歷史留在 gitea)。版號自 1.0.0 重啟。 本外掛只有通用引擎4 entitypost / 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_DISABLEDintegration 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 containerintegration 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:

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.

['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_INTERCEPTORSregister_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:

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_Registrywpdo_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 0N WP authentication reads sessions directly; Hook Bus cannot intercept session creation safely
_application_passwords 01 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.