- dual_write / shadow_read no longer write flat in the update_/add_/
delete_{type}_metadata pre-filters, before WP decides whether native
changes at all. added_/updated_/deleted_{type}_meta (priority 1) call
TMDO_Hook_Bus::sync_from_native(), which copies the entity's first native
row (plain SQL, NULL when gone) into flat. Flat now follows native when WP
skips (same value), refuses (non-matching prev_value, add $unique on an
existing key) or deletes (also delete_all: touched IDs are collected from
the meta IDs in delete_{type}_meta), and a flat value left stale by a
direct SQL write heals on the next write.
- update_metadata_by_mid key changes: the old key is remembered in
update_{type}_metadata_by_mid and re-synced too.
- wpdo_after_write / wpdo_after_delete fire from the sync with the same
arguments; aeav_only keeps the pre-filter short-circuit unchanged.
- $internal_ops lock key includes the entity type (user 5 ≠ post 5).
- Live suite resync: 4 entities × 2 modes × 3 scenarios (old code: 16 of
24 FAIL); integration: pre-filter leaves flat to the sync, delete_all
re-syncs every touched user.
- Verify: live resync + shadow_write + serialized + zero_side_effect 33/33
PASS; unit 687 OK, integration 485 OK, phpcs (phpcs.xml) 0, stan OK
- PR: PR-057
Live test suite
Tests that must run inside a real WordPress site, because the stubbed
tests/integration bootstrap cannot reproduce:
- WP core meta flows (
update_metadata()reads the old value throughget_metadata_raw()before writing — the shadow_read write-loss bug lives there) - the SQL that
WP_Queryactually builds, and hook priorities - HivePress / WooCommerce behaviour (
pre_get_postssort clauses, empty-key meta reads, direct-SQL stock updates)
Never run against a production site. Suites create and delete fixtures
(tmdo_live_* users, tmdo-live-* terms). Entity modes are overridden in
process through pre_option_wpdo_bridge_modes; nothing is written to
wpdo_bridge_modes or wpdo_hook_bus_enabled.
The reference site is dev36 (HivePress + Marketplace + WooCommerce), with this
plugin symlinked from the plugins-dev worktree while developing.
Run
wp --path=/var/www/Studio/wp-local-dev36 eval-file tests/live/run.php <suite> [<suite> ...]
Exit code 0 when every test passed, 1 otherwise. Output per test:
| Status | Meaning |
|---|---|
| PASS / FAIL | normal |
| XFAIL | known bug, test marked xfail with the PR that fixes it; not counted as a failure |
| XPASS | an xfail test passed — counted as a failure so the fixing PR removes the marker |
| SKIP | the site does not register the key / taxonomy the test needs |
Suites
| Suite | Covers |
|---|---|
shadow_write |
writes in shadow_read reach native meta (user, term, HivePress listing) |
serialized |
array meta values are stored serialized in flat tables, never "Array" |
resync |
dual_write / shadow_read: flat follows native after every write (4 entities × 2 modes: update, same value, delete, refused writes, stale flat, key rename by meta_id) |
zero_side_effect |
zone tables that were never created are never queried on save / delete |
Tools (tools/)
Benchmark and data-flow helpers used for the 2026-09-26 dev36 integration test.
Output files go to the current directory (qbench) or $TMDO_LIVE_OUT
(default /tmp, run_batch.sh), never into the repo.
| Tool | Use |
|---|---|
seed.php |
wp eval-file seed.php <vendors|listings|requests|reviews|products|orders|terms> <n> [tag] — seed through real HivePress / WC APIs |
run_batch.sh |
run_batch.sh <tag> [divisor] — parallel seeding batch (TMDO_LIVE_WP_PATH, TMDO_LIVE_OUT) |
clone.sql |
bench only: clone hp_listing rows to reach large scale (sed "s/__K__/<n>/" clone.sql | wp db query) |
checker.php |
wp eval-file checker.php [entity] — native vs flat consistency per registered key (SQL only) |
qbench.php / qdiff.php |
run 16 WP_Query shapes, then diff two runs (native vs routed): result sets, order, latency, SQL errors |
mut.php |
data-flow mutations through HivePress models, WC CRUD and core APIs; prints native / flat / API values |
fe.sh + zz-s36-probe.php |
front-end search probe (TMDO_LIVE_URL). Copy the probe into wp-content/mu-plugins/ only for the run and remove it afterwards; it only answers requests carrying X-S36-Probe: 1 |