- TMDO_Logger::notice() is always written to the error log (it had no
callers); bridge_mode_changed and snapshot_deleted use it. Since PR-095
info() is silent without WP_DEBUG_LOG, which left no record of who switched
a mode or deleted a snapshot (security review L1)
- Verify: LoggerTest (notice always logged, info gated), unit 703,
integration 585, phpcs, stan
- PR: PR-113 (2.3 acceptance security review)
- TMDO_Cache_Layer::flush_group() bumps the post type's cold cache version
(option wpdo_cold_cache_version) instead of wp_cache_flush_group(), which
Redis Object Cache runs as one server-side scan that blocks Redis on a large
group; entries under older keys expire by their TTL
- TMDO_Zone_Cold::cache_key() builds every cold blob key; version 0 keeps the
current keys, so upgrading invalidates nothing
- Verify: CacheLayerTest (new keys, no scan, other post types untouched),
unit 702, integration 583, phpcs, stan
- PR: PR-110
- rest_exposable() also withholds keys named like an email, phone, address or
IP, even when a field declares show_in_rest (defence in depth)
- Verify: RestApiTest (private and look-alike public keys), unit 701,
integration 577, phpcs, stan; dev36 live rest suite
- PR: PR-096
- TMDO_Logger::info()/notice() write to the PHP error log only with
WP_DEBUG_LOG (filter tmdo_log_info); every CLI run and production request
printed [WPDO][INFO] lines
- debug() keeps its WP_DEBUG gate; warning() and error() are unchanged
- Verify: LoggerTest (2 new), unit 700, integration 577, phpcs, stan
- PR: PR-095
- blocked POST /listings/{id}/view requests update wpdo_rl_stats 1 in 10,
each sample adding 10: anonymous traffic no longer turns 1:1 into DB writes,
and the admin top list stays representative (unbiased estimate)
- Verify: RestApiTest counts option writes (about 20 for 200 blocked requests),
unit 698, integration 577, phpcs, stan
- PR: PR-094
- WooCommerce reads product meta directly and writes stock by SQL, so a flat copy drifted (R-2.1 V1 T4); product meta now stays in native only (plan decision 2). TMDO_Post_Fields registers wp_core, attachment and nav_menu_item. Existing wp_wpdo_post_wc_product tables are left in place and still dropped on uninstall; the orphan restore registers nothing for them while native meta holds the keys; `wp tmdo bridge-restore-native post --group=wc_product --retire` archives them.
- Tests that route products register the former group themselves (tmdo_test_register_wc_product_group(), same name and table); live router Q06 runs on wp_core's _wp_page_template instead of _sku. The post stress tester already skips flat tables that do not exist.
- Verify: PostFieldsRegistrationTest (RED before); dev36: wc_product absent from the registry and not restored as an orphan, a CLI order took stock 452 → 450 in native with no DB error (reverted), drift post 10 groups 0.
- PR: PR-077
- doctor: entity flat tables come from the registry (TMDO_CLI::check_entity_tables()) instead of a hard-coded user list naming the never-registered sso group, which failed doctor on every site; it also reports Promotion Guard violations and the recorded drift status per entity (report_guard_and_drift()).
- term-comment-diagnose: filter status comes from each filter's is_enabled() (the garbage filter and misc bucket default to off but were shown enabled); a filter whose add-on is not active shows "not installed".
- bridge-status: flat_rows counts rows holding a value (wp_core keeps an all-NULL row per post: 15306% on dev36) and eav_ids of a post-type group counts only its post types.
- Tests: the WP_CLI stubs record their output in $GLOBALS['_wp_cli_output'].
- Verify: DoctorTest, CliFilterStatusTest, CliBridgeStatusTest (RED before); dev36 doctor exit 1 → 0, wp_core 15306% → 100%.
- PR: PR-076
- The shadow diff logger records at most 20 rows per entity type + key per hour across all objects, on top of the existing per-object 5-minute window: a key stale on every object (WooCommerce total_sales, written by direct SQL) no longer logs a row per product read.
- The auto-promoter's third condition is now TMDO_Drift_Check::clean_since(): every backfillable group of the entity checked within 2 days (sampled or full) with no drift; no record, no promotion. The shadow diff count no longer gates it (the filter's 4th argument is passed as 0). The Promotion Guard still refuses HivePress / WooCommerce sites.
- Reason classification was dropped (decision 2026-09-27): it needs a schema change and drift-check already reports miss / diff / extra per key.
- Verify: ShadowDiffThrottleTest, AutoPromoterDriftTest (RED before); dev36 simulated user shadow_read: clean_since yes, promotion still refused by the guard, mode unchanged.
- PR: PR-075
- New `wp tmdo bridge-mode <entity> <mode>` goes through TMDO_Mode_Manager::set(): unsafe jumps and aeav_only promotions refused by the Promotion Guard exit with the reason and leave the mode unchanged. Named apart from the Feature Flags `mode-set`.
- member-sso-sync and member-force-logout warn that they are deprecated: the user sso group they work on is no longer registered.
- Verify: CliBridgeModeTest (RED: method missing); dev36 bridge-mode user aeav_only refused (unsafe jump), term dual_write → shadow_read → aeav_only refused by the HivePress guard → back to disabled, wpdo_bridge_modes identical to before.
- PR: PR-069
- show_in_rest defaulted to true, so GET /wpdo/v1/listings and /listings/{id} (public by design) returned every registered hot / cold field to anonymous visitors — e.g. infocards tmeetic_ig_access_token_enc / _token_expiry / _user_id and WooCommerce _purchase_note (confirmed on dev36)
- Fields are private unless registered with show_in_rest => true; protected (underscore) and credential-like keys (token, secret, password, api key, private key, credential, salt, nonce) are never exposed
- Zone A list path no longer returns whole rows when nothing is public, and filters / orderby accept public columns only
- Version 1.1.1; CHANGELOG, readme
- Verify: unit 690 (+3), integration 525 (+3 private-column tests, RED on 1.1.0), live suite rest 2/2 (RED on 1.1.0), all live 49/49, phpcs 0, stan OK
- TMDO_Zone_Router::delete_post() deleted from wp_wpdo_hot_/cold_{post_type}
whenever fields were registered for that zone, so every post delete on a
HivePress site (cold fields registered, module idle, table never created)
logged a "table doesn't exist" error. Hot and cold deletes now also need
the table to exist; module state is not checked, so a table left by a
rolled-back module still loses the rows of deleted posts.
- Live suite zero_side_effect: saving / updating / deleting an hp_listing
issues no query on missing zone tables (covers PR-054 and this PR).
- Verify: ZoneRouterTest 22 OK; live zero_side_effect + shadow_write +
serialized 9/9 PASS (old code: zero_side_effect FAIL); unit 687 OK,
integration 484 OK, phpcs (phpcs.xml) 0, stan OK
- PR: PR-054b
- TMDO_Type_Caster: text / textarea values that are arrays or objects are
stored with maybe_serialize() (was (string) → the literal "Array", which
bridge-restore-native then wrote back over native meta); from_db()
unserializes them with TMDO_Safe_Unserialize. text columns are
VARCHAR(255): longer values are stored NULL (native keeps them) and
logged as text_overflow.
- Add `wp tmdo repair-array-literals [--entity] [--confirm]`
(TMDO_Array_Literal_Repair): rewrites flat "Array" values from native
meta and lists native rows that are "Array" themselves (backup needed).
- Verify: TypeCasterSerializeTest 13 OK, ArrayLiteralRepairTest 3 OK;
unit 685 OK, integration 483 OK, phpcs (phpcs.xml) 0, stan OK;
live `serialized` 3 PASS; dev36 repair dry-run lists the value corrupted
by the 2026-09-26 round trip (user #1 community-events-location)
- PR: PR-055
- TMDO_Cache_Layer::prefetch() / warm_post() (and so on_save_post /
on_loop_start) queried wp_wpdo_cold_{post_type} whenever cold fields
were registered, even with the cold module idle and the table never
created: every HivePress / WC save logged "table doesn't exist" errors.
They now return unless the module is write-active and the table exists.
- CacheLayerTest activates the module in setUp; new cases assert zero cold
queries while idle or when the table is missing.
- Verify: CacheLayerTest 12 OK; unit 671 OK, integration 480 OK,
phpcs (phpcs.xml) 0, stan OK; dev36 live: seeding listings/products/
vendors with cold_hp_listing idle → 0 wpdo_cold errors (was hundreds)
- PR: PR-054
- Add TMDO_Promotion_Guard: HivePress models read every meta key with an
empty-key get_*_meta() and WooCommerce does the same for products (and
writes stock/sales with direct SQL), so on those sites native meta must
stay authoritative. Promotion to aeav_only is refused in
TMDO_Mode_Manager::set() (covers REST, CLI, phases, auto-promoter) for
post/user/term/comment with HivePress and post/user with WooCommerce;
TMDO_Post_Migration::cleanup() and the cleanup phase refuse too.
Demotion stays open; filter tmdo_promotion_guard_allow overrides.
- Admin notice lists guarded entities already in aeav_only with the
restore-native command (no forced demotion on read).
- Verify: PromotionGuardTest 8 OK; unit 668 OK, integration 480 OK,
phpcs (phpcs.xml) 0, stan OK; dev36 live: 4 promotions + post-cleanup
refused, wpdo_bridge_modes unchanged
- PR: PR-053
- missing_integration_addons() no longer asks for the SSO AddOn when
2meet-hub-core / 2meet-spoke-sso are active: they register their own
tables and the `sso` values belong in usermeta (benchmark: no
measurable gain from the flat table for by-user-id access).
- `sso` joins the deprecated AddOns; for hub / spoke / sso the notice says
they go without replacement and to run
`wp tmdo bridge-restore-native user --group=sso --retire` first.
- Verify: core unit 658 / integration 479 green, phpcs clean
- PR: PR-043
- TMDO_Member_Fields no longer registers user/sso; the definition moved
unchanged (hash 03543f19) to 2meet-data-optimizer-sso-addon.
- missing_integration_addons(): 2meet-hub-core or 2meet-spoke-sso without
the SSO AddOn is reported (admin notice + Site Health).
- The deprecated-AddOn notice points hub / spoke users to the SSO AddOn.
- Sites must activate the SSO AddOn before this ships (PR-041); aeav_only
sites without it fall back to orphan-group restore.
- Verify: core unit 659 / integration 475, SSO 6, HP 142/49, WC 10/22
green, phpcs clean
- PR: PR-040
- ADR-003 step 4 (core side): TMDO_Schema_Registry::register() raises
_doing_it_wrong() the first time each provider registers a zone=hot
field in a request (zone omitted defaults to hot). Fields still
register, so existing hot data stays readable.
- Cold / warm / archive registrations are unaffected.
- Test bootstrap's _doing_it_wrong stub now records calls.
- Verify: core unit 658 / integration 459, HP 142/49, WC 10/22 green,
phpcs clean
- PR: PR-038
- register_group() accepts $args['indexes'] (name => ordered keys); indexes on
unknown keys are dropped. Kept outside the field definition, so schema
hashes are unchanged.
- Schema_Manager::ensure_group_indexes() adds missing cidx_* indexes to the
existing flat table in place; the applied definition is remembered in
tmdo_entity_indexes so later requests skip SHOW INDEX.
- Bench (1M listings, scenario 2): Entity p50 337 ms -> 278 ms with
(hp_verified, hp_price), on par with Hot (269 ms).
- Verify: core unit 656 / integration 459 green, phpcs clean
- PR: PR-027
- TMDO_Compatibility::missing_integration_addons() reports active HivePress / WooCommerce whose AddOn bootstrap class is absent
- Surface it as Site Health test wpdo_missing_integration_addons (also on the admin Doctor tab) and an administrator notice (TMDO_Missing_Addons_Notice)
- Verify: MissingIntegrationAddonsTest (5, class stand-ins in separate processes); core unit 651 / integration 449, HP 148, WC 10 OK; read-only check: dev20/21 HivePress+WooCommerce, dev22 HivePress, others clean
- PR: PR-035
- Add docs/PARTNER-REGISTRATION.md: TMDO_Anti_EAV_Aware bridge setup (plugins_loaded:3), table and entity-group registration with post_types scopes, frozen definitions, and the AddOn → self-registration migration checked with [COLLISION]/[ORPHAN]
- TMDO_Deprecated_Addons_Notice warns administrators while any of the 8 in-house partner AddOns is active (site or network)
- Trait register_entity_fields() forwards an optional $args (post_types) to the registry
- Verify: DeprecatedAddonsNoticeTest (4); core unit 645 / integration 449, HP 148, WC 10 OK
- PR: PR-021
- TMDO_Post_Fields::post_type_for_group() / groups_for_post_type() derive HivePress-style groups from their registry post_type scope (core keeps its own attachment / wc_product / nav_menu_item map); the three copy-pasted switches and the query router's POST_TYPE_GROUP_MAP use them
- Term/comment backfill, shadow verifier and CLI take their groups from the registry (TMDO_Term_Comment_Backfill::registered_groups()); installer drops the hp_* entries already covered by its table patterns; REST listings default post type is filterable (tmdo_rest_default_post_type, default 'post')
- Verify: NoHpMappingsTest guard + mapping/default tests; core unit 640 / integration 449, HP 146/49, WC 10/22 OK
- PR: PR-020e
- Feature flags keep only the live AddOn modules (wc_orders, latepoint) as ADDON_MODULES / addon_modules(); drop map_hpct_status() and the 7 HivePress HPCT modules whose interceptors were removed in PR-014
- Remove HPCT detection/notice (Compatibility, core), the admin HPCT import tab and status row, CLI import-hpct, the 7 advisor rules for dead modules, and the removed interceptors from COEXIST_WHITELIST
- Verify: NoHpctLegacyTest guard; core unit 635 / integration 449, HP 145/49, WC 10/22 OK; all sites have every module idle and never imported HPCT
- PR: PR-020c
- Drop hp_listing_core / hp_request_core / hp_vendor_core from TMDO_Post_Fields and hp_user from TMDO_Member_Fields; the HivePress AddOn registers identical definitions (PR-019/020a/033b)
- Tests: core asserts it registers none of them; HP-specific core tests removed (covered by the AddOn); SyncBridgeEntityGuardTest registers its own hp_price group
- Verify: core unit 633 / integration 449, HP 145/49, WC 10/22 OK
- Deploy: activate the HivePress AddOn on dev20/21/22 first
- PR: PR-020b
- TMDO_Post_Group_Reconciler copies non-NULL values that legacy routing stored in another group's table into the post type's own group (NULL/missing legacy values keep the scoped value), resumable via a cursor, with a catch-up pass for rows updated during the run, then switches on tmdo_post_scoped_routing
- CLI: wp tmdo post-reconcile-groups [--dry-run] [--batch=<n>]; registry exposes get_group_post_types()
- Verify: PostGroupReconcilerPlanTest (4), PostGroupReconcilerTest (6); core unit 635 / integration 449, HP 145/49, WC 10/22 OK; read-only dry-run: dev20 320 values / 0 overwrites, dev21/22 none
- PR: PR-034
- Entity groups can declare post_types; get_field() resolves a post's key against its own type's groups first, then unscoped ones (legacy last-wins lookup kept for callers without a post type)
- Hook Bus and the post query router pass the post type, so a listing's hp_rating / hp_status / hp_verified map to hp_listing_core instead of the vendor/request tables; core HP groups declare their post types (schema hashes unchanged)
- Behind tmdo_post_scoped_routing: on for sites without post flat rows, off where rows may sit in the wrong table until PR-034 reconciles them (dry-run: dev20–22 off, dev23/24/30/31/40 on)
- Verify: EntityRegistryScopeTest (8), PostScopedRoutingTest (4); core unit 630 / integration 443, HP 144/49, WC 10/22 OK
- PR: PR-033
- On init:1, re-register aeav_only groups whose flat table exists and whose definition is in wpdo_registry_meta but no active plugin registers them, so their data stays readable (routing only, never DDL)
- Keys owned by a live group, or with rows in native meta (newer data written while orphaned), are left alone; the native-meta check is cached for a day
- Surface restored groups via Site Health (wpdo_orphaned_entity_groups), the admin Doctor tab, and wp wpdo doctor
- Verify: EntityRegistryRestoreTest (9), OrphanGroupRestoreTest (7); core unit 622 / integration 439, HP 141/49, WC 10/22 OK; dry-run on dev20–22 restores 16/17/4 groups, keeps hp_taxonomy hp_sort_order/hp_icon on native
- PR: PR-018
- register_group() keeps the first definition but records a field diff (added/removed/changed) when the same entity/group is re-registered differently; identical re-registration stays silent
- Surface collisions via Site Health (wpdo_entity_group_collisions), the admin Doctor tab, and wp wpdo doctor; warning log throttled to once a day
- Verify: EntityRegistryCollisionTest (7); core unit 613 / integration 432, HP 141/49, WC 10/22 OK
- PR: PR-017
- GET /wpdo/v1/listings rejects unregistered or non-viewable post types (400) and joins posts so Zone A only lists published posts of that type
- WP_Query fallback now returns only show_in_rest hot columns, matching the Zone A path
- Verify: core unit 605 / integration 432 OK (new RestApi tests); 1M bench: page 7ms, total COUNT 0.12s → 1.7s (tracked in PR-030)
- PR: PR-015
- install() no longer switches the Hook Bus on or puts user/term/comment
into dual_write; the now-unused maybe_autoactivate_entity_bridge() is gone
- Hook Bus and Mode_Manager defaults are off/disabled; sites with stored
modes (incl. aeav_only) keep them since only unset keys use defaults
- Guard run_dbdelta()'s upgrade.php require like install_v2_tables() so
install() can run under the integration test stub
Verify: InstallerV2Test::test_install_does_not_enable_entity_bridge,
ModeManagerDefaultsTest; unit 602 OK, integration 421 OK
PR: PR-007
- Seed a HivePress-shaped dataset three ways (native meta, entity tables,
hot table) with flat tables built by TMDO's own schema code
- Refuse any database not named *_bench before the bootstrap connects;
bootstrap now honours a pre-defined ABSPATH for the wp-admin stub
- Full scale seeds 9.5M postmeta rows in 7m25s; 0 value mismatches
Verify: TMDO_TEST_DB_NAME=wp_wpdo_bench php tests/bench/seed.php
--vendors=100000 --listings=1000000; unit 600 OK, integration 416 OK
PR: PR-004