From 4ff4e2f2875a18bc2951c77294e32c20429afb05 Mon Sep 17 00:00:00 2001 From: MrDarkRoot Date: Tue, 27 Oct 2026 11:51:14 +0700 Subject: [PATCH 1/6] compat(free,#280): pair-rule support for tested WP 6.7/6.8 + Woo 9.x --- .../workflows/wordpress-woo-acceptance.yml | 42 +++++++++ wordpress/release/CLAIM-MATRIX.md | 32 ++++--- wordpress/tests/acceptance/EVIDENCE.md | 15 +++ wordpress/tests/acceptance/docker-compose.yml | 2 + wordpress/tests/acceptance/in-container.sh | 10 +- .../tests/acceptance/legacy-recovery.php | 3 +- wordpress/tests/acceptance/run.sh | 6 +- wordpress/tests/current-core/in-container.sh | 5 +- wordpress/tests/dependency-63.php | 37 ++++++-- wordpress/tests/free/unit.php | 18 ++++ wordpress/tests/historical-minimum-121.php | 6 +- wordpress/tests/identity-62.php | 6 +- wordpress/tests/in-container.sh | 19 ++-- .../release/candidate-install-verify.php | 4 +- .../tests/release/historical-shim-cases.php | 4 +- wordpress/tests/release/package-preflight.php | 2 +- wordpress/tests/release/readme-validate.php | 18 +++- wordpress/tests/release/woo-candidate.php | 17 ++-- .../includes/free/class-change-plan.php | 5 +- .../free/class-free-support-contract.php | 92 +++++++++++++------ wordpress/writeleash/languages/writeleash.pot | 6 +- wordpress/writeleash/readme.txt | 6 +- wordpress/writeleash/writeleash.php | 4 +- 23 files changed, 261 insertions(+), 98 deletions(-) diff --git a/.github/workflows/wordpress-woo-acceptance.yml b/.github/workflows/wordpress-woo-acceptance.yml index d277f70e..06cdd225 100644 --- a/.github/workflows/wordpress-woo-acceptance.yml +++ b/.github/workflows/wordpress-woo-acceptance.yml @@ -44,6 +44,46 @@ jobs: sizes: '100' woo: '9.9.7' cache: persistent + - name: legacy-wp67-woo90 + core: wordpress:6.7.2-php8.2-apache@sha256:85c7d25eaf9726eeff21adad930aa43d2894bd73e9253ab250737b6d994a1181 + cli: wordpress:cli-php8.2@sha256:aaa79b1239f24fccbb1634ec0a25c3e4fac6271bc9bf2334c40c3c8ef77bce89 + sizes: '100 101 1000' + woo: '9.0.4' + cache: default + memory: 256M + time: '60' + variations: '1' + - name: legacy-wp67-woo90-redis + core: wordpress:6.7.2-php8.2-apache@sha256:85c7d25eaf9726eeff21adad930aa43d2894bd73e9253ab250737b6d994a1181 + cli: wordpress:cli-php8.2@sha256:aaa79b1239f24fccbb1634ec0a25c3e4fac6271bc9bf2334c40c3c8ef77bce89 + sizes: '100' + woo: '9.0.4' + cache: persistent + memory: 256M + time: '60' + variations: '1' + - name: legacy-wp68-woo99 + core: wordpress:6.8.3-php8.2-apache@sha256:1e6215749283955d5c9ffea6c297651ed23cdfdbb91677ad7abd705b2682f2cf + cli: wordpress:cli-php8.2@sha256:aaa79b1239f24fccbb1634ec0a25c3e4fac6271bc9bf2334c40c3c8ef77bce89 + sizes: '100 101 1000' + woo: '9.9.7' + cache: default + memory: 256M + time: '60' + variations: '1' + - name: below-wordpress + core: wordpress:6.6.2-php8.2-apache@sha256:c30c1376b4d2c9d1e2328c2ee6149d133104d65873a98cbc6adc743a2964c4ba + cli: wordpress:cli-php8.2@sha256:aaa79b1239f24fccbb1634ec0a25c3e4fac6271bc9bf2334c40c3c8ef77bce89 + sizes: '100' + woo: '9.0.4' + cache: default + expect: wp-floor + - name: below-woo + core: wordpress:7.1.2-php8.2-apache@sha256:da4c3e7b0051b70835b0a188ecdd5bce5ebf6f83d1f3df41553f1a97d237fd19 + cli: wordpress:cli-php8.2@sha256:aaa79b1239f24fccbb1634ec0a25c3e4fac6271bc9bf2334c40c3c8ef77bce89 + sizes: '100' + woo: '8.9.5' + cache: default - name: php74 core: wordpress:7.1.2-php8.2-apache@sha256:da4c3e7b0051b70835b0a188ecdd5bce5ebf6f83d1f3df41553f1a97d237fd19 cli: wordpress:cli-php7.4@sha256:9a1bb40df37cfe9c82b3d098a845b05f3586deefb696d1b59abcce3c269fb075 @@ -77,6 +117,8 @@ jobs: WL112_CACHE: ${{ matrix.cache }} WL112_MEMORY: ${{ matrix.memory || '128M' }} WL112_TIME: ${{ matrix.time || '30' }} + WL112_VARIATIONS: ${{ matrix.variations || '' }} + WL112_EXPECT: ${{ matrix.expect || '' }} WL112_RESULTS: ${{ runner.temp }}/wl112 shell: bash run: | diff --git a/wordpress/release/CLAIM-MATRIX.md b/wordpress/release/CLAIM-MATRIX.md index 019f50c7..2b11605e 100644 --- a/wordpress/release/CLAIM-MATRIX.md +++ b/wordpress/release/CLAIM-MATRIX.md @@ -69,28 +69,30 @@ Status values: ## 5. WooCommerce version -* **COPY CLAIM:** "WooCommerce 10.0 through 11.x." -* **SUPPORTING ISSUE:** #108/#111 execution gate; #112 compatibility row; #119 support boundary; #180 supported range. +* **COPY CLAIM:** "WooCommerce 10.0 through 11.x on WordPress 7.0 up to 8.0 only. Legacy band: WooCommerce 9.0.0 below 10.0.0 on WordPress 6.7 below 7.0. Exercised legacy points: WordPress 6.7.2 with WooCommerce 9.0.4, and WordPress 6.8.3 with WooCommerce 9.9.7." +* **SUPPORTING ISSUE:** #108/#111 execution gate; #112 compatibility row; #119 support boundary; #180 supported range; #280 pair rule. * **SUPPORTING TEST/EVIDENCE:** * `FREE-PRICE-APPLY-CONTRACT.md` — Woo 10.0 through 11.x storage/API basis (CPT data store, lookup shape, CRUD price setters, cache helpers; ProductCache guarded for pre-10.5); official 11.1.2 ZIP SHA-256 recorded as the exercised point. - * `wordpress/tests/acceptance/EVIDENCE.md` — row `7.1.2 / 11.0.1 / 8.2.34` → `UNSUPPORTED: existing execution gate; zero applied` (now out-of-range refusal with a typed reason). - * `wordpress/writeleash/includes/free/class-free-support-contract.php` — `WOOCOMMERCE_MIN = '10.0.0'`, `WOOCOMMERCE_MAX_EXCLUSIVE = '12.0.0'`. + * `wordpress/tests/acceptance/EVIDENCE.md` — modern rows remain the prior evidence. #280 (2026-10-27) adds the legacy pairs and the refusals; it does not re-execute the modern cells. + * `wordpress/writeleash/includes/free/class-free-support-contract.php` — modern `WOOCOMMERCE_MIN = '10.0.0'`, `WOOCOMMERCE_MAX_EXCLUSIVE = '12.0.0'`; legacy `LEGACY_WOOCOMMERCE_MIN = '9.0.0'`, `LEGACY_WOOCOMMERCE_MAX_EXCLUSIVE = '10.0.0'`. A frozen plan must stay on the same band. * Canonical immutable artifact: https://downloads.wordpress.org/plugin/woocommerce.11.1.2.zip → `Version: 11.1.2`, `Requires at least: 7.0`; SHA-256 `9de9350a1cf5671b9960afb3151f40f7980e223217a441bf2ea5921b5fce8e9e`. -* **ALLOWED WORDING:** "WooCommerce 10.0 through 11.x." -* **REQUIRED LIMITATION:** No versions below 10.0.0 or at/above 12.0.0; the Admin refuses out-of-range versions before creating jobs. -* **STATUS:** PUBLISHABLE. + * Legacy floor artifact: https://downloads.wordpress.org/plugin/woocommerce.9.0.4.zip SHA-256 `230ba96dbd288c98ce76e8ce430a7d52ec0994596717bde978b5613839ff8333`. Legacy high pair reconfirmed: https://downloads.wordpress.org/plugin/woocommerce.9.9.7.zip SHA-256 `96facedd12e32b6e0b120dcaa4a62b9c6b1ae12159d32510ea416c8e9b44f4dd`. +* **ALLOWED WORDING:** "WooCommerce 10.0 through 11.x" for the modern line, plus the two named legacy pairs and the pair rule. Do not say WooCommerce 9.0 through 11.x. +* **REQUIRED LIMITATION:** Previous public floor was WooCommerce 10.0.0 with no legacy band (through 2026-10-27). No WooCommerce below 9.0.0 on the legacy band, no WordPress below 6.7, no WooCommerce 9 on WordPress 7, no WooCommerce 10 or newer on WordPress 6.7 or 6.8, and nothing at or above 12.0.0. Out-of-range pairs are refused before job creation. The two bands are not a rectangle. Untested versions inside a certified band are not a coverage map and are not called refused. +* **STATUS:** PUBLISHABLE WITH LIMITATION. ## 6. WordPress versions and minimum metadata -* **COPY CLAIM:** "WordPress 7.0.1 and 7.1.2 were exercised. Requires WordPress 7.0 or newer, the minimum of the supported WooCommerce package." -* **SUPPORTING ISSUE:** #112 compatibility rows; #121 metadata decision. +* **COPY CLAIM:** "WordPress 7.0.1 and 7.1.2 were exercised on WooCommerce 10.0 through 11.x. Legacy band: WordPress 6.7 below 7.0 with WooCommerce 9.0.0 below 10.0.0. Exercised: WordPress 6.7.2 with WooCommerce 9.0.4, and WordPress 6.8.3 with WooCommerce 9.9.7. Requires WordPress 6.7 only on that legacy WooCommerce band; WordPress 7.0 up to 8.0 is required with WooCommerce 10.0 through 11.x." +* **SUPPORTING ISSUE:** #112 compatibility rows; #121 metadata decision; #280 pair rule. * **SUPPORTING TEST/EVIDENCE:** - * `wordpress/tests/acceptance/EVIDENCE.md` — exact rows for WP `7.1.2` and `7.0.1`; `6.8.3` recorded as `UNSUPPORTED: Woo package requires WP 7.0`. - * Exact Woo package header (`Requires at least: 7.0`) and `https://api.wordpress.org/plugins/info/1.0/woocommerce.json` (`requires: 7.0`), checked 2026-10-02. - * `readme-validate.php` requires both exact fixtures and the 7.0 floor, and requires the plugin header `Requires at least: 7.0` to match. - * `wordpress/tests/dependency-63.php`, foundation `in-container.sh` and current-core `in-container.sh`: exact public WP 6.8.3 expected Core minimum refusal before any historical shim; public WP 7.1.2 dependency proof with missing Woo and active Woo 11.1.2. See `wordpress/release/PHASE-B-121.md` for the separate repository-only historical mechanism. -* **ALLOWED WORDING:** "Requires WordPress 7.0 or newer" as directory metadata plus the exact tested fixtures. -* **REQUIRED LIMITATION:** Do not claim all WordPress 7.x releases are supported from two tested points; directory metadata is a minimum, not a coverage map. + * `wordpress/tests/acceptance/EVIDENCE.md` — exact rows for WP `7.1.2` and `7.0.1`. Before #280, `6.8.3` was recorded as `UNSUPPORTED: Woo package requires WP 7.0` because the modern Woo package refuses WordPress below 7.0. That refusal still holds for WooCommerce 10 and 11. #280 exercises 6.7.2 and 6.8.3 only with WooCommerce 9.x. + * Modern Woo package header (`Requires at least: 7.0`) and `https://api.wordpress.org/plugins/info/1.0/woocommerce.json` (`requires: 7.0`), checked 2026-10-02. WooCommerce 9.9.7 header `Requires at least: 6.7`; WooCommerce 9.0.4 header `Requires at least: 6.4`. + * `readme-validate.php` requires the exact fixtures `7.0.1` and `7.1.2`, the directory floor `Requires at least: 6.7`, and the modern line "WooCommerce 10.0 through 11.x". + * `wordpress/tests/dependency-63.php` and foundation `in-container.sh`: WordPress 6.8.3 with WooCommerce 9.9.7 is a legacy-band dependency activation, not a core minimum refusal. The one below-floor WordPress refusal is 6.6.x (`plugin_wp_incompatible`, minimum 6.7). current-core `in-container.sh` keeps the public WordPress 7.1.2 plus WooCommerce 11.1.2 dependency proof. See `wordpress/release/PHASE-B-121.md` for the separate repository-only historical mechanism. + * Image digests: `wordpress:6.7.2-php8.2-apache@sha256:85c7d25eaf9726eeff21adad930aa43d2894bd73e9253ab250737b6d994a1181`, `wordpress:6.8.3-php8.2-apache@sha256:1e6215749283955d5c9ffea6c297651ed23cdfdbb91677ad7abd705b2682f2cf`, `wordpress:6.6.2-php8.2-apache@sha256:c30c1376b4d2c9d1e2328c2ee6149d133104d65873a98cbc6adc743a2964c4ba`. +* **ALLOWED WORDING:** The tested fixtures and the pair rule. "Requires WordPress 6.7" is directory metadata for the legacy band only, not a promise that every WordPress 6.7+ site is supported. +* **REQUIRED LIMITATION:** Previous directory floor was WordPress 7.0 (through 2026-10-27). Do not claim all WordPress 6.7, 6.8, or 7.x releases from the tested points. Untested versions inside a certified band are not a coverage map and are not called refused. No WordPress below 6.7. No WooCommerce 9 on WordPress 7. No WooCommerce 10 or newer on WordPress 6.7 or 6.8. Out-of-range pairs are refused before job creation. * **STATUS:** PUBLISHABLE WITH LIMITATION. ## 7. PHP versions and minimum metadata diff --git a/wordpress/tests/acceptance/EVIDENCE.md b/wordpress/tests/acceptance/EVIDENCE.md index 259107cc..33932203 100644 --- a/wordpress/tests/acceptance/EVIDENCE.md +++ b/wordpress/tests/acceptance/EVIDENCE.md @@ -240,3 +240,18 @@ upgrade certification, guaranteed shopper prices or generic rollback. **#77/#76/#65/#66 modified/executed: NO. Publication: NO. Merge: NO.** Existing regression CI, including the historically named release matrix, is regression evidence only; it is not execution or authorization of that chain. + +## #280 pair-rule rows (2026-10-27) + +Runtime bytes: worktree HEAD `796fe096f090cd7d6256eb9bce94df2eecf6bd41` plus the uncommitted pair-rule diff. `git diff` SHA-256 at the passing reruns, before this evidence section: `3d89431bafd7ce03e63e8235bc95be084c8addb252ebe2b1cfec8e419a72eed7`. Clean `796fe096` does not contain the pair rule. PHP 8.2.34. MySQL 8.0.44. MariaDB 10.11.15. CLI `wordpress:cli-php8.2@sha256:aaa79b1239f24fccbb1634ec0a25c3e4fac6271bc9bf2334c40c3c8ef77bce89`. Modern WP 7.1.2 + WooCommerce 11.1.2 was not re-executed. Pair B persistent cache was not run. + +| Pair | Result | Cache | Sizes | Engines | Flows | +| --- | --- | --- | --- | --- | --- | +| WP 6.7.2 `sha256:85c7d25eaf9726eeff21adad930aa43d2894bd73e9253ab250737b6d994a1181` + WC 9.0.4 `230ba96dbd288c98ce76e8ce430a7d52ec0994596717bde978b5613839ff8333` | PASS | default | 100, 101, 1000 | MySQL and MariaDB | Journey `#112` PASS (preview, approve with scheduler down, resume, external conflict at 75, history, undo of 2, sale 70 to 75 and back to 70). Variations JSON PASS (apply 2, undo 2, conflicted sibling). `#207`/`#208` PASS. Legacy recovery PASS, 100 restored, 1 conflict preserved. | +| Same pair | PASS | Redis 7.4.2 / redis-cache 2.7.0 `0cbc41dea351688693b8e7db1165b5d92b6c8ef7231ac0ff5abec7b83d62c635` | 100 | MySQL and MariaDB | Same journey, variations, sale, and legacy recovery PASS. | +| WP 6.8.3 `sha256:1e6215749283955d5c9ffea6c297651ed23cdfdbb91677ad7abd705b2682f2cf` + WC 9.9.7 `96facedd12e32b6e0b120dcaa4a62b9c6b1ae12159d32510ea416c8e9b44f4dd` | PASS | default | 100, 101, 1000 | MySQL and MariaDB | Same journey, variations, sale, and legacy recovery PASS. Persistent cache on this pair: NOT_TESTED. | +| WP 7.1.2 `sha256:da4c3e7b0051b70835b0a188ecdd5bce5ebf6f83d1f3df41553f1a97d237fd19` + WC 9.9.7 | UNSUPPORTED_EARLY | persistent | 100 | MySQL and MariaDB | `woocommerce_version_unsupported`. Actual job size 0. Parity: unchanged prices and zero WriteLeash evidence. Presets: unsupported Woo refusal PASS. | +| WP 7.1.2 + WC 8.9.5 `260abd0d6dfd8afe0e10fb64d72b071be3b5623de8af072265758b35380b428b` | UNSUPPORTED_EARLY | default | 100 | MySQL and MariaDB | Same refusal and zero WriteLeash product writes. | +| WP 6.6.2 `sha256:c30c1376b4d2c9d1e2328c2ee6149d133104d65873a98cbc6adc743a2964c4ba` + WC 9.0.4 | PASS as Core refusal | default | n/a | MySQL and MariaDB | `#280 WP 6.6.2 PUBLIC ACTIVATION: EXPECTED CORE REFUSAL (minimum 6.7 first)`. `plugin_wp_incompatible`. Plugin inactive. This is WordPress core, not the pair-rule journey. | + +Transcripts: `/tmp/wl280-results-pairA`, `/tmp/wl280-results-pairB`, `/tmp/wl280-results-neg-cross`, `/tmp/wl280-results-neg-woo8`, `/tmp/wl280-results-neg-wp66`, `/tmp/wl280-results-pairA-redis`. The first Pair A attempt failed in `legacy-recovery.php` because the probe hashed null `plan_json` on journal schema 4; the probe now uses `plan_fingerprint` for schema 3 and 4. That attempt is `/tmp/wl280-results-pairA-fail1` and is not a PASS. diff --git a/wordpress/tests/acceptance/docker-compose.yml b/wordpress/tests/acceptance/docker-compose.yml index 0bda7834..815c5b73 100644 --- a/wordpress/tests/acceptance/docker-compose.yml +++ b/wordpress/tests/acceptance/docker-compose.yml @@ -47,6 +47,8 @@ services: WL112_CACHE: ${WL112_CACHE:-persistent} WL112_MEMORY: ${WL112_MEMORY:-128M} WL112_TIME: ${WL112_TIME:-30} + WL112_VARIATIONS: ${WL112_VARIATIONS:-} + WL112_EXPECT: ${WL112_EXPECT:-} volumes: - ./cache:/opt/woo-zips:ro - ${WL112_RESULTS}:/evidence diff --git a/wordpress/tests/acceptance/in-container.sh b/wordpress/tests/acceptance/in-container.sh index 2910b0c7..49ea124e 100644 --- a/wordpress/tests/acceptance/in-container.sh +++ b/wordpress/tests/acceptance/in-container.sh @@ -13,6 +13,12 @@ for host in mysql mariadb; do wp --path="$site" config set WP_MAX_MEMORY_LIMIT "$WL112_MEMORY" wp --path="$site" core install --url=http://127.0.0.1:8080 --title=Acceptance --admin_user=admin --admin_password=disposable_admin_password --admin_email=admin@example.test --skip-email wp --path="$site" plugin install "/opt/woo-zips/woocommerce.$WL112_WOO.zip" --activate + # Below the WordPress floor, Core refuses activation before any job. One + # explicit refusal lives in dependency-63 phase minimum (WordPress 6.6.x). + if [ "${WL112_EXPECT:-}" = wp-floor ]; then + CC63_PHASE=minimum wp --path="$site" eval-file /opt/tests/dependency-63.php + continue + fi # Core Admin passes an explicit boolean network-wide flag. The archived PHP # 7.4 CLI command passes null instead; exercise the actual public WP activation # API with the Admin's false flag consistently on every PHP build. @@ -39,10 +45,12 @@ for host in mysql mariadb; do done if [ "$WL112_WOO" = 11.1.2 ]; then wp --path="$site" eval-file /opt/tests/acceptance/support-boundaries.php - wp --path="$site" eval-file /opt/tests/acceptance/variations.php wp --path="$site" eval-file /opt/tests/acceptance/dirty-catalog.php wp --path="$site" eval-file /opt/tests/acceptance/torture.php fi + if [ "$WL112_WOO" = 11.1.2 ] || [ "${WL112_VARIATIONS:-}" = 1 ]; then + wp --path="$site" eval-file /opt/tests/acceptance/variations.php + fi wp --path="$site" eval-file /opt/tests/acceptance/presets.php wp --path="$site" eval-file /opt/tests/acceptance/legacy-recovery.php kill "$web_pid" diff --git a/wordpress/tests/acceptance/legacy-recovery.php b/wordpress/tests/acceptance/legacy-recovery.php index 9a68253a..29dbb28e 100644 --- a/wordpress/tests/acceptance/legacy-recovery.php +++ b/wordpress/tests/acceptance/legacy-recovery.php @@ -23,8 +23,9 @@ function lr_report( array $report ): void { } function lr_journal_binding( array $job ): array { global $wpdb; + // v3 and v4 rows leave plan_json null and bind through plan_fingerprint. $rows = $wpdb->get_results( $wpdb->prepare( - 'SELECT product_id,schema_version,plan_id,plan_schema_version,plan_hash_version,plan_hash,price_field,CASE WHEN schema_version=3 THEN plan_fingerprint ELSE SHA2(plan_json,256) END AS material_hash,expected_price,target_price FROM %i WHERE plan_id=%s ORDER BY product_id', + 'SELECT product_id,schema_version,plan_id,plan_schema_version,plan_hash_version,plan_hash,price_field,CASE WHEN schema_version IN (3,4) THEN plan_fingerprint ELSE SHA2(plan_json,256) END AS material_hash,expected_price,target_price FROM %i WHERE plan_id=%s ORDER BY product_id', J::table( $wpdb ), $job['plan_id'] ), ARRAY_A ); foreach ( $rows as $row ) { wl112_assert( $row['material_hash'] === hash( 'sha256', $job['plan_json'] ), 'legacy journal retains exact canonical job binding' ); } diff --git a/wordpress/tests/acceptance/run.sh b/wordpress/tests/acceptance/run.sh index 3d720fd8..c32b0351 100644 --- a/wordpress/tests/acceptance/run.sh +++ b/wordpress/tests/acceptance/run.sh @@ -20,8 +20,12 @@ fetch() { } fetch woocommerce.11.1.2.zip 9de9350a1cf5671b9960afb3151f40f7980e223217a441bf2ea5921b5fce8e9e fetch woocommerce.11.0.1.zip da189b6616c610d15a2106f93151dab81b78f83e075bcefce221ac0d00b4fa21 -# Out-of-range refusal proof: the newest Woo below the supported 10.0 floor. +# Legacy-band high pair, and the cross-band refusal on WordPress 7. fetch woocommerce.9.9.7.zip 96facedd12e32b6e0b120dcaa4a62b9c6b1ae12159d32510ea416c8e9b44f4dd +# Legacy-band floor: newest wordpress.org WooCommerce 9.0.x. +fetch woocommerce.9.0.4.zip 230ba96dbd288c98ce76e8ce430a7d52ec0994596717bde978b5613839ff8333 +# Below the legacy Woo floor: newest wordpress.org WooCommerce 8.x. +fetch woocommerce.8.9.5.zip 260abd0d6dfd8afe0e10fb64d72b071be3b5623de8af072265758b35380b428b fetch redis-cache.2.7.0.zip 0cbc41dea351688693b8e7db1165b5d92b6c8ef7231ac0ff5abec7b83d62c635 compose=(docker compose -p writeleash_woo112 -f "$here/docker-compose.yml") cleanup() { diff --git a/wordpress/tests/current-core/in-container.sh b/wordpress/tests/current-core/in-container.sh index 38514596..dbd0608e 100644 --- a/wordpress/tests/current-core/in-container.sh +++ b/wordpress/tests/current-core/in-container.sh @@ -2,7 +2,10 @@ # #63 current-stable compatibility gate: WordPress 7.1.2 + Redirection 5.5.2 + # the repository-only historical lab on both pinned database engines. Focused on # the certified product path and lifecycle; the full adversarial matrix remains -# pinned to the WordPress 6.8.3 baseline (#62). +# pinned to the WordPress 6.8.3 baseline (#62). WordPress 6.8.3 is not an +# out-of-range refusal: with WooCommerce 9.x it is the legacy dependency band. +# The one below-floor WordPress refusal is 6.6.x. This file's public +# dependency proof stays WordPress 7.1.2 with WooCommerce 11.1.2. set -eu for host in mysql mariadb; do site="/tmp/cc63-$host" diff --git a/wordpress/tests/dependency-63.php b/wordpress/tests/dependency-63.php index 9527546b..86d58aaa 100644 --- a/wordpress/tests/dependency-63.php +++ b/wordpress/tests/dependency-63.php @@ -23,10 +23,10 @@ } }; $data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin, false, false ); -$assert( '7.0' === $data['RequiresWP'], 'public WP minimum was shimmed or reverted' ); +$assert( '6.7' === $data['RequiresWP'], 'public WP minimum was shimmed or reverted' ); $assert( false === strpos( file_get_contents( WP_PLUGIN_DIR . '/' . $plugin ), 'HISTORICAL TEST ONLY' ), 'historical shim in public fixture' ); global $wp_version, $wpdb; -if ( 'minimum' !== $phase ) { +if ( 'minimum' !== $phase && 'legacy-supported' !== $phase ) { $assert( in_array( $wp_version, array( '7.0.1', '7.1.2' ), true ), 'public dependency proof must run on an exact supported WP point' ); } $snapshot = static function () use ( $wpdb ): string { @@ -48,24 +48,41 @@ switch ( $phase ) { case 'minimum': - $assert( '6.8.3' === $wp_version, 'unsupported fixture must stay exact WP 6.8.3' ); + $assert( 1 === preg_match( '/^6\.6\./', (string) $wp_version ), 'below-floor fixture must be WordPress 6.6.x, saw ' . $wp_version ); $assert( ! is_plugin_active( $plugin ), 'public plugin already active on unsupported WP' ); - // Woo is absent: seed stored product facts through Core for a non-vacuous - // zero-product-mutation assertion, without loading the public plugin. - register_post_type( 'product', array( 'public' => true ) ); - $product_id = wp_insert_post( array( 'post_type' => 'product', 'post_status' => 'publish', 'post_title' => '#121 refusal sentinel' ), true ); + // Woo may already be installed. Seed stored product facts through Core + // for a non-vacuous zero-product-mutation assertion. Core must refuse + // on the WordPress floor before the plugin loads. + if ( ! post_type_exists( 'product' ) ) { + register_post_type( 'product', array( 'public' => true ) ); + } + $product_id = wp_insert_post( array( 'post_type' => 'product', 'post_status' => 'publish', 'post_title' => '#280 refusal sentinel' ), true ); $assert( ! is_wp_error( $product_id ) && $product_id > 0, 'could not seed refusal product sentinel' ); update_post_meta( $product_id, '_regular_price', '123.45' ); update_post_meta( $product_id, '_price', '123.45' ); $before = $snapshot(); $result = activate_plugin( $plugin ); $assert( $result instanceof WP_Error && 'plugin_wp_incompatible' === $result->get_error_code(), 'WP minimum did not block activation first: ' . wp_json_encode( $result ) ); - $assert( false !== strpos( $result->get_error_message(), '7.0' ), 'minimum refusal does not name WP 7.0' ); - $assert( ! is_plugin_active( $plugin ), 'public plugin activated normally on WP 6.8.3' ); + $assert( false !== strpos( $result->get_error_message(), '6.7' ), 'minimum refusal does not name WP 6.7' ); + $assert( ! is_plugin_active( $plugin ), 'public plugin activated normally on WP ' . $wp_version ); $assert( $before === $snapshot(), 'Core refusal caused durable/product mutation' ); $assert( ! defined( 'WRITELEASH_VERSION' ), 'public plugin loaded despite minimum refusal' ); wp_delete_post( $product_id, true ); - echo "#121 WP 6.8.3 PUBLIC ACTIVATION: EXPECTED CORE REFUSAL (minimum 7.0 first), inactive, zero WriteLeash durable/product mutation: PASS\n"; + echo '#280 WP ' . $wp_version . " PUBLIC ACTIVATION: EXPECTED CORE REFUSAL (minimum 6.7 first), inactive, zero WriteLeash durable/product mutation: PASS\n"; + break; + + case 'legacy-supported': + // WordPress 6.7 up to 7.0 is a supported dependency only with the + // legacy WooCommerce band. This phase does not run a price journey. + $assert( version_compare( $wp_version, '6.7', '>=' ) && version_compare( $wp_version, '7.0', '<' ), 'legacy dependency proof must stay on WordPress 6.7 up to 7.0' ); + $assert( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '9.0.0', '>=' ) && version_compare( WC_VERSION, '10.0.0', '<' ), 'legacy dependency proof requires WooCommerce 9.x' ); + $assert( ! is_plugin_active( $plugin ), 'public plugin already active before legacy dependency proof' ); + $result = activate_plugin( $plugin ); + $assert( ! is_wp_error( $result ) && is_plugin_active( $plugin ), 'public activation failed on the legacy pair: ' . wp_json_encode( $result ) ); + $assert( 'legacy' === \WriteLeash\Free_Support_Contract::support_band( $wp_version, WC_VERSION ), 'legacy pair was not classified legacy' ); + $assert( \WriteLeash\Free_Support_Contract::woocommerce_ok(), 'legacy pair was refused by woocommerce_ok' ); + $assert( null === \WriteLeash\Free_Support_Contract::support_band( $wp_version, '11.1.2' ), 'WooCommerce 11 must stay refused on this WordPress band' ); + echo '#280 WP ' . $wp_version . ' + WooCommerce ' . WC_VERSION . " legacy dependency: public activation succeeded; WooCommerce 11 on this WordPress band stays unsupported: PASS\n"; break; case 'block': diff --git a/wordpress/tests/free/unit.php b/wordpress/tests/free/unit.php index a7feb441..3e74318a 100644 --- a/wordpress/tests/free/unit.php +++ b/wordpress/tests/free/unit.php @@ -107,3 +107,21 @@ try { \WriteLeash\Free_Support_Contract::assert_job_size( 1001 ); throw new RuntimeException( '1001 not refused' ); } catch ( \WriteLeash\Free_Job_Limit_Error $error ) { wl107_equal( $error->selected(), 1001, 'typed limit count' ); wl107_equal( $error->getMessage(), 'supported_job_limit_exceeded', 'typed limit reason' ); } wl107_marker( 'Free support contract accepts 1000 and refuses 1001; 1000-product plan computes and rehydrates' ); +$band280 = \WriteLeash\Free_Support_Contract::class; +wl107_equal( $band280::support_band( '7.1.2', '11.1.2' ), 'modern', 'modern pair' ); +wl107_equal( $band280::support_band( '7.0.1', '10.0.0' ), 'modern', 'modern floor pair' ); +wl107_equal( $band280::support_band( '7.1.2', '11.0.1' ), 'modern', 'modern previous woo' ); +wl107_equal( $band280::versions_compatible( '7.0.1', '10.5.0', '7.1.2', '11.1.2' ), true, 'modern drift stays on the modern band' ); +wl107_equal( $band280::support_band( '6.7.2', '9.0.4' ), 'legacy', 'legacy floor pair' ); +wl107_equal( $band280::support_band( '6.8.3', '9.9.7' ), 'legacy', 'legacy high pair' ); +wl107_equal( $band280::versions_compatible( '6.7.2', '9.0.4', '6.8.3', '9.9.7' ), true, 'legacy drift stays on the legacy band' ); +wl107_equal( $band280::versions_compatible( '7.1.2', '11.1.2', '6.8.3', '9.9.7' ), false, 'modern plan must not apply on the legacy band' ); +wl107_equal( $band280::versions_compatible( '6.8.3', '9.9.7', '7.1.2', '11.1.2' ), false, 'legacy plan must not apply on the modern band' ); +wl107_equal( $band280::support_band( '7.1.2', '9.9.7' ), null, 'WC 9 on WP 7 is refused' ); +wl107_equal( $band280::support_band( '6.8.3', '11.1.2' ), null, 'WC 11 on WP 6.8 is refused' ); +wl107_equal( $band280::support_band( '6.6.2', '9.0.4' ), null, 'WP below 6.7 is refused' ); +wl107_equal( $band280::support_band( '7.1.2', '8.9.5' ), null, 'WC below 9 is refused' ); +wl107_equal( $band280::support_band( '8.0', '11.1.2' ), null, 'WP 8 is refused' ); +wl107_equal( $band280::support_band( '7.1.2', '12.0.0' ), null, 'WC 12 is refused' ); +wl107_equal( $band280::woo_supported( '9.9.7' ) && $band280::wp_supported( '7.1.2' ) && null === $band280::support_band( '7.1.2', '9.9.7' ), true, 'independent halves must not authorize the rectangle' ); +wl107_marker( 'pair rule: two bands, cross-band and out-of-range refused' ); diff --git a/wordpress/tests/historical-minimum-121.php b/wordpress/tests/historical-minimum-121.php index 2a0da090..8da15ca8 100644 --- a/wordpress/tests/historical-minimum-121.php +++ b/wordpress/tests/historical-minimum-121.php @@ -27,8 +27,8 @@ $before = hash_file( 'sha256', $public ); $readme_before = hash_file( 'sha256', $public_readme ); $text = file_get_contents( $main ); -$text = preg_replace( '/^( \* Requires at least:) 7\.0$/m', '$1 6.8.3' . "\n * HISTORICAL TEST ONLY: disposable WP 6.8.3 metadata shim; not public support.", $text, -1, $count ); -$readme_text = preg_replace( '/^Requires at least: 7\.0$/m', 'Requires at least: 6.8.3', file_get_contents( $readme ), -1, $readme_count ); +$text = preg_replace( '/^( \* Requires at least:) 6\.7$/m', '$1 6.8.3' . "\n * HISTORICAL TEST ONLY: disposable WP 6.8.3 metadata shim; not public support.", $text, -1, $count ); +$readme_text = preg_replace( '/^Requires at least: 6\.7$/m', 'Requires at least: 6.8.3', file_get_contents( $readme ), -1, $readme_count ); // Official Plugin Check audits this historical staged tree too. Keep its // metadata coherent without changing any production file or public assertion. if ( 1 !== $count || 1 !== $readme_count || false === file_put_contents( $main, $text ) || @@ -36,4 +36,4 @@ hash_file( 'sha256', $public ) !== $before || hash_file( 'sha256', $public_readme ) !== $readme_before ) { throw new RuntimeException( 'HISTORICAL TEST ONLY: exact metadata shim failed or public source changed' ); } -echo "#121 HISTORICAL TEST ONLY: staged header/readme WP minimum 6.8.3; production remains 7.0; no runtime authority added: PASS\n"; +echo "#121 HISTORICAL TEST ONLY: staged header/readme WP minimum 6.8.3; production remains 6.7; no runtime authority added: PASS\n"; diff --git a/wordpress/tests/identity-62.php b/wordpress/tests/identity-62.php index 107c7bf5..69f5dc47 100644 --- a/wordpress/tests/identity-62.php +++ b/wordpress/tests/identity-62.php @@ -13,11 +13,11 @@ $data = get_plugin_data( $file, false, false ); $main = file_get_contents( $file ); $historical = false !== strpos( $main, 'HISTORICAL TEST ONLY: disposable WP 6.8.3 metadata shim; not public support.' ); -$minimum = $historical ? '6.8.3' : '7.0'; +$minimum = $historical ? '6.8.3' : '6.7'; if ( $historical && ( '6.8.3' !== $GLOBALS['wp_version'] || 0 !== strpos( realpath( ABSPATH ), '/tmp/' ) || - ! preg_match( '/^ \* Requires at least: 7\.0$/m', file_get_contents( '/opt/writeleash/writeleash.php' ) ) ) ) { - throw new RuntimeException( '#121 historical identity requires disposable exact WP 6.8.3 and untouched production minimum 7.0' ); + ! preg_match( '/^ \* Requires at least: 6\.7$/m', file_get_contents( '/opt/writeleash/writeleash.php' ) ) ) ) { + throw new RuntimeException( '#121 historical identity requires disposable exact WP 6.8.3 and untouched production minimum 6.7' ); } $license_header = get_file_data( $file, diff --git a/wordpress/tests/in-container.sh b/wordpress/tests/in-container.sh index 2648bb7d..b6009888 100644 --- a/wordpress/tests/in-container.sh +++ b/wordpress/tests/in-container.sh @@ -19,19 +19,20 @@ echo "Database fixture: $db $server_version; WordPress $(wp --path="$site" core [ "$(wp --path="$site" core version)" = 6.8.3 ] [ "$(php -r 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;')" = 8.2 ] -# Public package MUST refuse normally on unsupported WP, before the shim. -CC63_PHASE=minimum wp --path="$site" eval-file /opt/tests/dependency-63.php +# WordPress 6.8.3 is inside the legacy band, so Core must not refuse the +# public package here. The one below-floor WordPress refusal is dependency-63 +# phase minimum on WordPress 6.6.x. This image proves the legacy dependency +# only: WooCommerce 9.9.7 activates WriteLeash, and WooCommerce 11 on this +# WordPress band stays unsupported. No price journey runs in this suite. php /opt/tests/release/readme-validate.php "$site/wp-content/plugins/writeleash" -# HISTORICAL TEST ONLY: shim only the disposable copy, then overlay the legacy -# substrate. Public dependency proof is on 7.1.2/11.1.2, never 6.8.3/9.9.7. -php /opt/tests/historical-minimum-121.php "$site" +wp --path="$site" plugin install /opt/plugin-zips/woocommerce.9.9.7.zip --activate +CC63_PHASE=legacy-supported wp --path="$site" eval-file /opt/tests/dependency-63.php +wp --path="$site" plugin deactivate writeleash +wp --path="$site" option delete writeleash_version +wp --path="$site" option delete writeleash_runner_state php /opt/tests/legacy/stage.php "$site/wp-content/plugins/writeleash" wp --path="$site" plugin install /opt/plugin-zips/redirection.5.5.2.zip --activate --force wp --path="$site" eval-file /opt/tests/redirection-schema.php -# 6.8.3-leg activation shim (see run.sh): Woo 9.9.7 satisfies Core's slug -# gate so the Guard regression leg can activate. Free behavior is never -# exercised against this build; #111 evidence stays 11.1.2 on WP 7.1.2. -wp --path="$site" plugin install /opt/plugin-zips/woocommerce.9.9.7.zip --activate wp --path="$site" eval-file /opt/tests/identity-62.php php /opt/tests/release/source-audit.php "$site/wp-content/plugins/writeleash" diff --git a/wordpress/tests/release/candidate-install-verify.php b/wordpress/tests/release/candidate-install-verify.php index 8a1e8e2a..a912f780 100644 --- a/wordpress/tests/release/candidate-install-verify.php +++ b/wordpress/tests/release/candidate-install-verify.php @@ -105,10 +105,10 @@ foreach ( array( 'Name' => 'WriteLeash', 'Version' => $evidence['VERSION'], - 'RequiresWP' => '7.0', + 'RequiresWP' => '6.7', 'RequiresPHP' => '7.4', 'RequiresPlugins' => 'woocommerce', - 'WCRequires' => '10.0', + 'WCRequires' => '9.0', 'WCTested' => '11.1', 'TextDomain' => 'writeleash', ) as $field => $wanted ) { diff --git a/wordpress/tests/release/historical-shim-cases.php b/wordpress/tests/release/historical-shim-cases.php index 8bd092ec..d3442926 100644 --- a/wordpress/tests/release/historical-shim-cases.php +++ b/wordpress/tests/release/historical-shim-cases.php @@ -28,9 +28,9 @@ copy( $public_readme, $readme ); $run( 'independent exact historical copy accepted; production untouched', true ); $staged = file_get_contents( $main ); - $expected = str_replace( ' * Requires at least: 7.0', " * Requires at least: 6.8.3\n * HISTORICAL TEST ONLY: disposable WP 6.8.3 metadata shim; not public support.", $original ); + $expected = str_replace( ' * Requires at least: 6.7', " * Requires at least: 6.8.3\n * HISTORICAL TEST ONLY: disposable WP 6.8.3 metadata shim; not public support.", $original ); if ( $expected !== $staged ) { throw new RuntimeException( 'Historical shim changed executable body' ); } - if ( str_replace( 'Requires at least: 7.0', 'Requires at least: 6.8.3', $original_readme ) !== file_get_contents( $readme ) ) { + if ( str_replace( 'Requires at least: 6.7', 'Requires at least: 6.8.3', $original_readme ) !== file_get_contents( $readme ) ) { throw new RuntimeException( 'Historical shim changed readme beyond metadata compatibility' ); } $run( 'second shim/previously modified copy rejected', false ); diff --git a/wordpress/tests/release/package-preflight.php b/wordpress/tests/release/package-preflight.php index 5d87a419..37e09884 100644 --- a/wordpress/tests/release/package-preflight.php +++ b/wordpress/tests/release/package-preflight.php @@ -94,7 +94,7 @@ $version_pattern = str_replace( '.', '\.', $release ); foreach ( array( '/^\s*\*\s*Version:\s*' . $version_pattern . '\s*$/m', - '/^\s*\*\s*Requires at least:\s*7\.0\s*$/m', + '/^\s*\*\s*Requires at least:\s*6\.7\s*$/m', '/^\s*\*\s*Requires PHP:\s*7\.4\s*$/m', '/^\s*\*\s*License:\s*GPL v2 or later\s*$/m', '/^\s*\*\s*License URI:\s*https:\/\/www\.gnu\.org\/licenses\/gpl-2\.0\.html\s*$/m', diff --git a/wordpress/tests/release/readme-validate.php b/wordpress/tests/release/readme-validate.php index c34933ba..aeb189d7 100644 --- a/wordpress/tests/release/readme-validate.php +++ b/wordpress/tests/release/readme-validate.php @@ -69,8 +69,9 @@ if ( ! $tags || count( $tags ) > 5 || count( $tags ) !== count( array_unique( $tags ) ) ) { $fail( 'Tags must be 1-5 unique comma-separated terms' ); } -if ( ! preg_match( '/\A\d+\.\d\z/', $headers['requires at least'] ) || '7.0' !== $headers['requires at least'] ) { - $fail( 'Requires at least must be the narrowest coherent value 7.0 (WooCommerce 11 package minimum)' ); +$wp_floor = $frozen ? '7.0' : '6.7'; +if ( ! preg_match( '/\A\d+\.\d\z/', $headers['requires at least'] ) || $wp_floor !== $headers['requires at least'] ) { + $fail( 'Requires at least must be ' . $wp_floor . ' (live tree: legacy-band floor 6.7; WooCommerce 10.0 through 11.x still requires WordPress 7.0 up to 8.0)' ); } if ( ! preg_match( '/\A\d+\.\d\z/', $headers['tested up to'] ) || '7.1' !== $headers['tested up to'] ) { $fail( 'Tested up to must be the tested current-stable major/minor 7.1' ); @@ -90,8 +91,9 @@ // Directory metadata and the main plugin header must not contradict each other. $main_header = (string) file_get_contents( $main ); +$header_floor = $frozen ? '7\.0' : '6\.7'; foreach ( array( - '/^\s*\*\s*Requires at least:\s*7\.0\s*$/m', + '/^\s*\*\s*Requires at least:\s*' . $header_floor . '\s*$/m', '/^\s*\*\s*Requires PHP:\s*7\.4\s*$/m', ) as $requirement ) { if ( ! preg_match( $requirement, $main_header ) ) { @@ -135,6 +137,16 @@ $fail( 'readme must name the exact tested WordPress core fixture: ' . $tested_core ); } } +if ( ! $frozen ) { + foreach ( array( 'WordPress 7.0 up to 8.0', '6.7.2', '9.0.4', '6.8.3', '9.9.7' ) as $pair_fact ) { + if ( ! str_contains( $text, $pair_fact ) ) { + $fail( 'readme must keep the modern line and name the tested legacy pairs: ' . $pair_fact ); + } + } + if ( false !== stripos( $text, 'WooCommerce 9.0 through 11.x' ) ) { + $fail( 'readme must not claim the untested WooCommerce 9.0 through 11.x rectangle' ); + } +} // #121 release-claim guard: the public listing must not widen the accepted // #111/#112/#177 evidence. The current supported new-job ceiling is 1,000 diff --git a/wordpress/tests/release/woo-candidate.php b/wordpress/tests/release/woo-candidate.php index cc2312c2..7824f344 100644 --- a/wordpress/tests/release/woo-candidate.php +++ b/wordpress/tests/release/woo-candidate.php @@ -42,11 +42,11 @@ 'Author URI' => 'https://profiles.wordpress.org/duyytrann', 'Developer' => 'Duy Tran', 'Developer URI' => 'https://profiles.wordpress.org/duyytrann', - 'Requires at least' => '7.0', + 'Requires at least' => '6.7', 'Tested up to' => '7.1', 'Requires PHP' => '7.4', 'Requires Plugins' => 'woocommerce', - 'WC requires at least' => '10.0', + 'WC requires at least' => '9.0', 'WC tested up to' => '11.1', 'Text Domain' => 'writeleash', 'Domain Path' => '/languages', @@ -94,12 +94,17 @@ ! preg_match( "/WOOCOMMERCE_MAX_EXCLUSIVE = '([^']+)'/", $contract, $max_match ) ) { $fail( 'unreadable WooCommerce support-contract range' ); } -$wc_min_header = '10.0'; -if ( $min_match[1] !== $wc_min_header . '.0' ) { - $fail( 'WC requires at least does not match WOOCOMMERCE_MIN ' . $min_match[1] ); +if ( '10.0.0' !== $min_match[1] || '12.0.0' !== $max_match[1] ) { + $fail( 'modern WooCommerce band must stay 10.0.0 inclusive to 12.0.0 exclusive, saw ' . $min_match[1] . ' to ' . $max_match[1] ); +} +if ( ! preg_match( "/WORDPRESS_MIN = '7\\.0'/", $contract ) || ! preg_match( "/WORDPRESS_MAX_EXCLUSIVE = '8\\.0'/", $contract ) ) { + $fail( 'modern WordPress band must stay 7.0 inclusive to 8.0 exclusive' ); +} +if ( ! preg_match( "/LEGACY_WORDPRESS_MIN = '6\\.7'/", $contract ) || ! preg_match( "/LEGACY_WORDPRESS_MAX_EXCLUSIVE = '7\\.0'/", $contract ) || ! preg_match( "/LEGACY_WOOCOMMERCE_MIN = '9\\.0\\.0'/", $contract ) || ! preg_match( "/LEGACY_WOOCOMMERCE_MAX_EXCLUSIVE = '10\\.0\\.0'/", $contract ) ) { + $fail( 'legacy pair band must stay WordPress 6.7 up to 7.0 and WooCommerce 9.0.0 up to 10.0.0' ); } if ( ! ( version_compare( '11.1', $min_match[1], '>=' ) && version_compare( '11.1', $max_match[1], '<' ) ) ) { - $fail( 'WC tested up to 11.1 is outside the support contract ' . $min_match[1] . ' to ' . $max_match[1] ); + $fail( 'WC tested up to 11.1 is outside the modern support contract ' . $min_match[1] . ' to ' . $max_match[1] ); } // B. Version coherence across every candidate identity surface. diff --git a/wordpress/writeleash/includes/free/class-change-plan.php b/wordpress/writeleash/includes/free/class-change-plan.php index 0ee0e917..4367265a 100644 --- a/wordpress/writeleash/includes/free/class-change-plan.php +++ b/wordpress/writeleash/includes/free/class-change-plan.php @@ -608,9 +608,8 @@ public function precondition( int $id, Product_Price_Snapshot $current, Price_St if ( $settings['currency'] !== $expected['currency'] || ! $settings['base_currency_context'] ) { $reasons[] = 'currency_context_changed'; } if ( $settings['price_decimals'] !== $expected['price_decimals'] ) { $reasons[] = 'price_decimals_changed'; } } - // Routine supported patch/minor updates do not change any guarded - // product/price fact, so they are not a conflict. Drift outside the - // supported range (or from an unparseable version) still is. + // Patch/minor drift inside one certified band is not a conflict. + // A different band, or a version outside both bands, still is. if ( ! Free_Support_Contract::versions_compatible( $expected['wordpress_version'], $expected['woocommerce_version'], $settings['wordpress_version'], $settings['woocommerce_version'] ) ) { $reasons[] = 'software_version_changed'; } return array( 'state' => $reasons ? 'CONFLICT' : 'MATCH', 'reasons' => $reasons ); } diff --git a/wordpress/writeleash/includes/free/class-free-support-contract.php b/wordpress/writeleash/includes/free/class-free-support-contract.php index a8192768..584320d5 100644 --- a/wordpress/writeleash/includes/free/class-free-support-contract.php +++ b/wordpress/writeleash/includes/free/class-free-support-contract.php @@ -24,63 +24,99 @@ final class Free_Support_Contract { public const MAX_STOCK_ABS_DELTA = '1000'; public const MIN_STOCK_FLOOR = '0'; /** - * Supported WooCommerce range. Every storage/API assumption WriteLeash - * relies on is stable across this span: CPT data-store CRUD price - * setters, the wc_product_meta_lookup min/max/onsale shape, the - * currency/decimals helpers, the public product-factory type/classname - * lookup and the transient/cache-group invalidation calls. Fresh reads - * construct the exact core product class directly and clean caches - * through public WordPress/Woo helpers only, so no version-conditional - * cache class is required anywhere in this span. The ceiling excludes the - * next major until its storage contract is re-certified. + * Certified pairs, not a rectangle. A WordPress version and a WooCommerce + * version are supported only together, on one of these two bands: + * + * Modern: WordPress >= 7.0 and < 8.0, AND WooCommerce >= 10.0.0 and < 12.0.0. + * Legacy: WordPress >= 6.7 and < 7.0, AND WooCommerce >= 9.0.0 and < 10.0.0. + * + * Every other combination is refused before a job is created, including + * WooCommerce 9 on WordPress 7, WooCommerce 10 or 11 on WordPress 6.7/6.8, + * WordPress below 6.7, and WooCommerce below 9.0.0. The modern storage/API + * assumptions (CPT data-store price setters, wc_product_meta_lookup shape, + * currency/decimals helpers, the public product factory, and public cache + * invalidation) were certified on that line. The legacy line is only the + * tested WordPress 6.7/6.8 plus WooCommerce 9.x pairs. Fresh reads still + * construct the core product class directly and clean caches through + * public helpers, so no version-conditional cache class is required. + * Ceilings stay exclusive until the next major is re-certified. + * WOOCOMMERCE_MIN and WORDPRESS_MIN are the modern floors, not the + * directory headers (those name the legacy floors). */ public const WOOCOMMERCE_MIN = '10.0.0'; public const WOOCOMMERCE_MAX_EXCLUSIVE = '12.0.0'; - /** - * Supported WordPress range. Matches the plugin header and WooCommerce - * 11's own minimum (WordPress 7.0); only long-stable WordPress APIs are - * used (options, WP_Query, roles/capabilities), so patch/minor drift - * inside this span is safe. - */ public const WORDPRESS_MIN = '7.0'; public const WORDPRESS_MAX_EXCLUSIVE = '8.0'; + public const LEGACY_WORDPRESS_MIN = '6.7'; + public const LEGACY_WORDPRESS_MAX_EXCLUSIVE = '7.0'; + public const LEGACY_WOOCOMMERCE_MIN = '9.0.0'; + public const LEGACY_WOOCOMMERCE_MAX_EXCLUSIVE = '10.0.0'; /** Safe to call during bootstrap: activation does not depend on Woo init. */ public static function woocommerce_reason(): ?string { if ( ! function_exists( 'wc_get_product' ) || ! defined( 'WC_VERSION' ) || ! did_action( 'woocommerce_init' ) ) { return 'woocommerce_unavailable'; } - return self::woo_supported( WC_VERSION ) ? null : 'woocommerce_version_unsupported'; + global $wp_version; + $wp = isset( $wp_version ) ? (string) $wp_version : ''; + return null !== self::support_band( $wp, (string) WC_VERSION ) ? null : 'woocommerce_version_unsupported'; } public static function woocommerce_ok(): bool { return null === self::woocommerce_reason(); } - /** Whether an installed WooCommerce version is inside the supported range. */ + /** + * Modern, legacy, or null. Null is refused. The halves are not + * interchangeable: WooCommerce 9 is legacy-only and WooCommerce 10/11 + * are modern-only. + */ + public static function support_band( string $wp, string $woo ): ?string { + if ( self::in_span( $wp, self::WORDPRESS_MIN, self::WORDPRESS_MAX_EXCLUSIVE ) && self::in_span( $woo, self::WOOCOMMERCE_MIN, self::WOOCOMMERCE_MAX_EXCLUSIVE ) ) { + return 'modern'; + } + if ( self::in_span( $wp, self::LEGACY_WORDPRESS_MIN, self::LEGACY_WORDPRESS_MAX_EXCLUSIVE ) && self::in_span( $woo, self::LEGACY_WOOCOMMERCE_MIN, self::LEGACY_WOOCOMMERCE_MAX_EXCLUSIVE ) ) { + return 'legacy'; + } + return null; + } + + /** + * Woo half of some certified band. Not a support decision: WooCommerce + * 9.9.7 is true here and still refused on WordPress 7. + */ public static function woo_supported( string $version ): bool { - return '' !== $version && version_compare( $version, self::WOOCOMMERCE_MIN, '>=' ) && version_compare( $version, self::WOOCOMMERCE_MAX_EXCLUSIVE, '<' ); + return self::in_span( $version, self::LEGACY_WOOCOMMERCE_MIN, self::LEGACY_WOOCOMMERCE_MAX_EXCLUSIVE ) + || self::in_span( $version, self::WOOCOMMERCE_MIN, self::WOOCOMMERCE_MAX_EXCLUSIVE ); } - /** Whether a WordPress version is inside the supported range. */ + /** + * WordPress half of some certified band. Not a support decision: + * WordPress 6.8 is true here and still refused with WooCommerce 10 or newer. + */ public static function wp_supported( string $version ): bool { - return '' !== $version && version_compare( $version, self::WORDPRESS_MIN, '>=' ) && version_compare( $version, self::WORDPRESS_MAX_EXCLUSIVE, '<' ); + return self::in_span( $version, self::LEGACY_WORDPRESS_MIN, self::LEGACY_WORDPRESS_MAX_EXCLUSIVE ) + || self::in_span( $version, self::WORDPRESS_MIN, self::WORDPRESS_MAX_EXCLUSIVE ); } /** - * Routine patch/minor drift inside the supported ranges does not change - * any product/price fact WriteLeash guards, so a frozen plan stays valid - * across it. Drift that leaves the supported range (on either side, at - * either timestamp) is not routine and stays a conflict. + * Patch/minor drift stays valid only inside one certified band. A frozen + * plan recorded on one band must not apply on the other, and a timestamp + * outside both bands is a conflict on either side. */ public static function versions_compatible( string $frozen_wp, string $frozen_woo, string $current_wp, string $current_woo ): bool { - return self::wp_supported( $frozen_wp ) && self::woo_supported( $frozen_woo ) && self::wp_supported( $current_wp ) && self::woo_supported( $current_woo ); + $frozen = self::support_band( $frozen_wp, $frozen_woo ); + $current = self::support_band( $current_wp, $current_woo ); + return null !== $frozen && $frozen === $current; } - /** Merchant-facing short form of the supported WooCommerce range. */ + /** Merchant-facing pair rule. Not a single WooCommerce 9.0 through 11.x rectangle. */ public static function range_text(): string { - /* translators: %s: minimum supported WooCommerce version. */ - return sprintf( __( 'WooCommerce %s through 11.x', 'writeleash' ), self::WOOCOMMERCE_MIN ); + return __( 'WooCommerce 10.0 through 11.x on WordPress 7.0 up to 8.0, or WooCommerce 9.0 below 10.0 on WordPress 6.7 below 7.0', 'writeleash' ); + } + + private static function in_span( string $version, string $min, string $max_exclusive ): bool { + return '' !== $version && version_compare( $version, $min, '>=' ) && version_compare( $version, $max_exclusive, '<' ); } /** diff --git a/wordpress/writeleash/languages/writeleash.pot b/wordpress/writeleash/languages/writeleash.pot index abd6955a..a3ff091d 100644 --- a/wordpress/writeleash/languages/writeleash.pot +++ b/wordpress/writeleash/languages/writeleash.pot @@ -2039,10 +2039,8 @@ msgstr "" msgid "No price changes yet. Preview a change to start." msgstr "" -#. translators: %s: minimum supported WooCommerce version. -#: includes/free/class-free-support-contract.php:66 -#, php-format -msgid "WooCommerce %s through 11.x" +#: includes/free/class-free-support-contract.php +msgid "WooCommerce 10.0 through 11.x on WordPress 7.0 up to 8.0, or WooCommerce 9.0 below 10.0 on WordPress 6.7 below 7.0" msgstr "" #: includes/free/class-job-state.php:115 diff --git a/wordpress/writeleash/readme.txt b/wordpress/writeleash/readme.txt index c3a78a47..340169fa 100644 --- a/wordpress/writeleash/readme.txt +++ b/wordpress/writeleash/readme.txt @@ -1,7 +1,7 @@ === WriteLeash === Contributors: duyytrann Tags: woocommerce, bulk edit, prices, variations, undo -Requires at least: 7.0 +Requires at least: 6.7 Tested up to: 7.1 Stable tag: 0.2.0 Requires PHP: 7.4 @@ -77,8 +77,8 @@ effects. * Up to 1,000 selected products per new job in the tested configuration. Variations count individually. * Published core simple products and published variations of published core variable products, in the base store currency. Parent prices are not edited directly. * Custom product types and universal extension compatibility are unsupported. -* WooCommerce 10.0 through 11.x (version 10.0.0 or newer, below 12.0.0). -* WordPress 7.0.1 and 7.1.2 were exercised. Requires WordPress 7.0 or newer. +* WooCommerce 10.0 through 11.x on WordPress 7.0 up to 8.0 only (10.0.0 or newer, below 12.0.0). +* WordPress 7.0.1 and 7.1.2 were exercised on that line. Legacy band: WordPress 6.7 below 7.0 with WooCommerce 9.0.0 below 10.0.0. Exercised: WordPress 6.7.2 with WooCommerce 9.0.4, and WordPress 6.8.3 with WooCommerce 9.9.7. Pairs outside either band are refused before a job is created. * PHP 7.4.33, 8.0.30, 8.1.34 and 8.2.34 were exercised. Requires PHP 7.4 or newer. * MySQL 8.0.44 and MariaDB 10.11.15 were exercised, with default and Redis Object Cache 2.7.0 (Redis 7.4.2) persistent cache modes. * WooCommerce refreshes variable parent price ranges after each variation change. diff --git a/wordpress/writeleash/writeleash.php b/wordpress/writeleash/writeleash.php index 654e340f..f208d537 100644 --- a/wordpress/writeleash/writeleash.php +++ b/wordpress/writeleash/writeleash.php @@ -8,11 +8,11 @@ * Author URI: https://profiles.wordpress.org/duyytrann * Developer: Duy Tran * Developer URI: https://profiles.wordpress.org/duyytrann - * Requires at least: 7.0 + * Requires at least: 6.7 * Tested up to: 7.1 * Requires PHP: 7.4 * Requires Plugins: woocommerce - * WC requires at least: 10.0 + * WC requires at least: 9.0 * WC tested up to: 11.1 * License: GPL v2 or later * License URI: https://www.gnu.org/licenses/gpl-2.0.html From 6e96ace5a75ff1e72d1bed8ccf00a7518a0cfe2f Mon Sep 17 00:00:00 2001 From: MrDarkRoot Date: Tue, 27 Oct 2026 11:59:55 +0700 Subject: [PATCH 2/6] docs(listing,#322): pair the overwrite listing with the #280 bands Keep the accidental price and stock overwrite listing, and state the tested legacy pairs without a WooCommerce 9.0 through 11.x rectangle. Redis on WordPress 6.8.3 with WooCommerce 9.9.7 stays untested. Stable tag remains 0.2.0. --- wordpress/release/CLAIM-MATRIX.md | 40 ++++++++++----- wordpress/tests/acceptance/EVIDENCE.md | 2 +- wordpress/writeleash/readme.txt | 68 +++++++++++++------------- 3 files changed, 63 insertions(+), 47 deletions(-) diff --git a/wordpress/release/CLAIM-MATRIX.md b/wordpress/release/CLAIM-MATRIX.md index 2b11605e..17907c1f 100644 --- a/wordpress/release/CLAIM-MATRIX.md +++ b/wordpress/release/CLAIM-MATRIX.md @@ -120,13 +120,13 @@ Status values: ## 9. Persistent object cache -* **COPY CLAIM:** "with default and Redis Object Cache 2.7.0 (Redis 7.4.2) persistent cache modes." -* **SUPPORTING ISSUE:** #108 cache verifier; #109/#110 cache suites; #112 cache/hook torture. +* **COPY CLAIM:** "MySQL 8.0.44 and MariaDB 10.11.15 were exercised. Redis Object Cache 2.7.0 (Redis 7.4.2) on the modern line and on 6.7.2 with 9.0.4 at 100 products, not on 6.8.3 with 9.9.7." +* **SUPPORTING ISSUE:** #108 cache verifier; #109/#110 cache suites; #112 cache/hook torture; #280 pair rule. * **SUPPORTING TEST/EVIDENCE:** - * `wordpress/tests/acceptance/EVIDENCE.md` — Redis 7.4.2, Redis Object Cache/drop-in 2.7.0; rollback-before-commit, retry, SIGKILL recovery and cache-repair parity passed. + * `wordpress/tests/acceptance/EVIDENCE.md` — Redis 7.4.2, Redis Object Cache/drop-in 2.7.0 on the modern line; rollback-before-commit, retry, SIGKILL recovery and cache-repair parity passed there. #280 adds Redis on WordPress 6.7.2 + WooCommerce 9.0.4 at size 100, both engines. WordPress 6.8.3 + WooCommerce 9.9.7 persistent cache is NOT_TESTED. * `FREE-PRICE-APPLY-CONTRACT.md` — Woo `ProductCache::remove()` plus targeted lookup/meta repair. -* **ALLOWED WORDING:** "default and Redis Object Cache 2.7.0 (Redis 7.4.2) persistent cache modes." -* **REQUIRED LIMITATION:** Exact fixture only; not a general "works with every caching plugin" promise. +* **ALLOWED WORDING:** Name the cache fixture only for the modern line and for WordPress 6.7.2 with WooCommerce 9.0.4 at 100 products. +* **REQUIRED LIMITATION:** Exact fixture only; not a general "works with every caching plugin" promise. Do not imply persistent cache was exercised on WordPress 6.8.3 with WooCommerce 9.9.7. * **STATUS:** PUBLISHABLE WITH LIMITATION. ## 10. Preview and approval binding @@ -233,12 +233,12 @@ Status values: ## 19. Requirements metadata coherence -* **COPY CLAIM (headers):** `Requires at least: 7.0`, `Tested up to: 7.1`, `Requires PHP: 7.4`, `Requires Plugins: woocommerce`, `Stable tag: 0.1.0`. -* **SUPPORTING ISSUE:** #120 package identity; #121 metadata; #112 evidence. -* **SUPPORTING TEST/EVIDENCE:** `readme-validate.php` compares readme and plugin-header requirements and fails on contradiction; `package-preflight.php` locks the header values; official readme validator result 2026-10-02 (notes only, no errors). -* **ALLOWED WORDING:** Directory metadata as above with rationale: 7.0 is the WooCommerce 11.1.2 package minimum; 7.4 is the WriteLeash/Woo minimum; `woocommerce` is the required dependency. -* **REQUIRED LIMITATION:** "Tested up to: 7.1" reflects the tested 7.1.2 point (directory ignores the patch) and must not be read as all-7.x coverage. -* **STATUS:** PUBLISHABLE. +* **COPY CLAIM (headers):** `Requires at least: 6.7`, `Tested up to: 7.1`, `Requires PHP: 7.4`, `Requires Plugins: woocommerce`, `WC requires at least: 9.0`, `Stable tag: 0.2.0`. +* **SUPPORTING ISSUE:** #120 package identity; #121 metadata; #112 evidence; #280 pair rule. +* **SUPPORTING TEST/EVIDENCE:** `readme-validate.php` compares readme and plugin-header requirements and fails on contradiction; `package-preflight.php` locks the header values. `Requires at least: 6.7` is the directory floor so WordPress can activate the legacy band. It is not a promise that every WordPress 6.7+ site is supported. WooCommerce 10.0 through 11.x still requires WordPress 7.0 up to 8.0. +* **ALLOWED WORDING:** Directory metadata as above. 6.7 is the legacy-band floor. 7.4 is the WriteLeash/Woo PHP minimum. `woocommerce` is the required dependency. Stable tag stays 0.2.0. +* **REQUIRED LIMITATION:** "Tested up to: 7.1" reflects the tested 7.1.2 point (directory ignores the patch) and must not be read as all-7.x coverage. "Requires at least: 6.7" and "WC requires at least: 9.0" are minimums, not a rectangle. +* **STATUS:** PUBLISHABLE WITH LIMITATION. ## 20. Restricted agent access (grant-bound seatbelt) @@ -257,15 +257,29 @@ Status values: * **REQUIRED LIMITATION:** Not a firewall or sandbox. Does not constrain administrators who are not grant actors, hostile plugins, direct SQL, third-party hook side effects, orders/refunds/coupons/tax/options or arbitrary meta. A user-0 system cron is outside the grant. Stock Undo is unsupported; Undo is price-only. No universal "agent-safe" claim. * **STATUS:** PUBLISHABLE WITH LIMITATION. +## 21. Accidental price and stock overwrite protection + +* **COPY CLAIM:** "Protection against accidental WooCommerce price and stock overwrites, with Preview, conflicts, History and eligible Undo." +* **SUPPORTING ISSUE:** #322 listing position. Price conflicts and eligible Undo use the accepted #107/#110/#112 evidence. The stock path uses the #310/#312 grant boundary, not a new write path. +* **SUPPORTING TEST/EVIDENCE:** + * Conflict behavior: `wordpress/tests/acceptance/EVIDENCE.md` and `wordpress/writeleash/includes/free/class-product-snapshot.php` (row 11). The check is the stored price WriteLeash is changing, or another checked setting, not every catalog edit. + * Eligible Undo: `wordpress/writeleash/FREE-UNDO-HISTORY-CONTRACT.md` and `wordpress/tests/acceptance/EVIDENCE.md` (rows 13 and 14). Restoration is price-only, when the saved details and current product state permit it. + * Stock controlled path: `wordpress/tests/jobs/stock-integration.php` grant path only (bounded SET/INCREASE_INT/DECREASE_INT inside a frame). Not a claim that every stock write is protected. Ordinary price operations still do not change stock (row 4). + * Restricted agent: `wordpress/tests/jobs/matrix-integration.php`, `wordpress/tests/jobs/perimeter-integration.php`, and `wordpress/tests/jobs/grant-integration.php`, as already listed in row 20. +* **ALLOWED WORDING:** The short description above. Say that merchant price jobs use Preview, conflict checks, History and eligible price Undo, and that WriteLeash stock changes run only on the opt-in controlled grant path. +* **REQUIRED LIMITATION:** This is not protection for every WooCommerce write, not direct SQL, not hostile plugins, and not a firewall. Stock Undo is unsupported. Eligible Undo does not reverse orders, completed sales, emails, webhooks, remote requests, external queues or other plugins' effects. +* **STATUS:** PUBLISHABLE WITH LIMITATION. + --- ## Forbidden public claims (must never appear) * 10,000 or more supported products; any size above the tested 1,000-product new-job ceiling as a support promise. -* Support for WooCommerce versions outside 10.0 through 11.x, or "all WooCommerce versions". -* "Old WordPress support" below 7.0 (the Woo 11.1.2 package refuses WP 6.8.3). +* WooCommerce outside the two certified bands, "all WooCommerce versions", or the rectangle "WooCommerce 9.0 through 11.x". +* WordPress below 6.7. WooCommerce 9 on WordPress 7. WooCommerce 10 or newer on WordPress 6.7 or 6.8. The WooCommerce 11.1.2 package still refuses WordPress below 7.0. * Real managed/shared-host certification. * Universal rollback; "rolls everything back"; "prevents all bad edits"; any protection claim for every Woo write. +* Reading the #322 listing as a firewall, hostile-plugin isolation, direct SQL protection, universal agent safety, or protection for every WooCommerce write. Stock wording covers only the opt-in controlled grant path. * Reversal of orders, sales, emails, webhooks, remote HTTP or external queues. * Guaranteed shopper prices; arbitrary save-hook throughput; arbitrary metadata size. * Package-upgrade certification from a released Free package (none exists). diff --git a/wordpress/tests/acceptance/EVIDENCE.md b/wordpress/tests/acceptance/EVIDENCE.md index 33932203..e71ab6c3 100644 --- a/wordpress/tests/acceptance/EVIDENCE.md +++ b/wordpress/tests/acceptance/EVIDENCE.md @@ -243,7 +243,7 @@ regression evidence only; it is not execution or authorization of that chain. ## #280 pair-rule rows (2026-10-27) -Runtime bytes: worktree HEAD `796fe096f090cd7d6256eb9bce94df2eecf6bd41` plus the uncommitted pair-rule diff. `git diff` SHA-256 at the passing reruns, before this evidence section: `3d89431bafd7ce03e63e8235bc95be084c8addb252ebe2b1cfec8e419a72eed7`. Clean `796fe096` does not contain the pair rule. PHP 8.2.34. MySQL 8.0.44. MariaDB 10.11.15. CLI `wordpress:cli-php8.2@sha256:aaa79b1239f24fccbb1634ec0a25c3e4fac6271bc9bf2334c40c3c8ef77bce89`. Modern WP 7.1.2 + WooCommerce 11.1.2 was not re-executed. Pair B persistent cache was not run. +Runtime bytes: worktree HEAD `796fe096f090cd7d6256eb9bce94df2eecf6bd41` plus the uncommitted pair-rule diff, later committed as `e8e14078c7fc2fa0ddb0c568c9c796edbb95bd5c` and cherry-picked onto `c7d5e8766ef10e5fdb255184b002c2b6fc6a4313`. `git diff` SHA-256 at the passing reruns, before this evidence section: `3d89431bafd7ce03e63e8235bc95be084c8addb252ebe2b1cfec8e419a72eed7`. Clean `796fe096` does not contain the pair rule. The journeys were not repeated after #329 merged. PHP 8.2.34. MySQL 8.0.44. MariaDB 10.11.15. CLI `wordpress:cli-php8.2@sha256:aaa79b1239f24fccbb1634ec0a25c3e4fac6271bc9bf2334c40c3c8ef77bce89`. Modern WP 7.1.2 + WooCommerce 11.1.2 was not re-executed. Pair B persistent cache was not run. | Pair | Result | Cache | Sizes | Engines | Flows | | --- | --- | --- | --- | --- | --- | diff --git a/wordpress/writeleash/readme.txt b/wordpress/writeleash/readme.txt index 340169fa..5d248876 100644 --- a/wordpress/writeleash/readme.txt +++ b/wordpress/writeleash/readme.txt @@ -1,6 +1,6 @@ === WriteLeash === Contributors: duyytrann -Tags: woocommerce, bulk edit, prices, variations, undo +Tags: woocommerce, prices, stock, variations, undo Requires at least: 6.7 Tested up to: 7.1 Stable tag: 0.2.0 @@ -8,18 +8,28 @@ Requires PHP: 7.4 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html -WooCommerce bulk price editor with Preview, newer-price conflict checks, History and eligible Undo. +Protection against accidental WooCommerce price and stock overwrites, with Preview, conflicts, History and eligible Undo. == Description == -**Change WooCommerce prices. Protect newer edits.** +WriteLeash offers protection against accidental WooCommerce price and stock +overwrites on paths you control. Preview the price change, approve it, and +Apply. If the stored price or another checked setting no longer matches the +Preview, WriteLeash leaves the newer value alone and marks a conflict. +History records the result. Eligible Undo restores prices WriteLeash applied +when the saved details and the current product state permit it. -Bulk edit prices with exact Preview before Apply; relevant price/setting -conflicts preserve newer values. Select by name, SKU or category, run -background jobs, Resume, review History, save presets and repeat an eligible -job into a fresh editable Preview. +Ordinary price jobs do not change stock. WriteLeash changes stock only on an +opt-in restricted grant, through its controlled price and stock job path, +which stays off until an operator issues the grant. Stock Undo is not +supported. -= Bulk edit Regular Price and Sale Price = +This is not protection for every WooCommerce write. It is not a firewall, it +does not isolate hostile plugins, and it does not stop direct SQL. Undo does +not reverse orders, completed sales, emails, webhooks, remote requests, +external queues or other plugins' effects. + += Regular Price and Sale Price = Set a price, add/subtract a fixed amount, or change by a percentage. Choose Regular or Sale Price. A variable parent means its variations, or choose @@ -68,19 +78,15 @@ window. Failed current checks leave the product alone as a conflict. Regular Price Undo preserves later sales where valid; Sale Price Undo checks its recorded Regular Price. Availability does not guarantee restoration. -Undo restores eligible prices only. It does not reverse orders, completed -sales, emails, webhooks, remote requests, external queues or other plugins' -effects. - == Supported scope == * Up to 1,000 selected products per new job in the tested configuration. Variations count individually. * Published core simple products and published variations of published core variable products, in the base store currency. Parent prices are not edited directly. * Custom product types and universal extension compatibility are unsupported. * WooCommerce 10.0 through 11.x on WordPress 7.0 up to 8.0 only (10.0.0 or newer, below 12.0.0). -* WordPress 7.0.1 and 7.1.2 were exercised on that line. Legacy band: WordPress 6.7 below 7.0 with WooCommerce 9.0.0 below 10.0.0. Exercised: WordPress 6.7.2 with WooCommerce 9.0.4, and WordPress 6.8.3 with WooCommerce 9.9.7. Pairs outside either band are refused before a job is created. +* Exercised: WordPress 7.0.1 and 7.1.2 on that line; WordPress 6.7.2 with WooCommerce 9.0.4; WordPress 6.8.3 with WooCommerce 9.9.7. Legacy band is WordPress 6.7 below 7.0 with WooCommerce 9.0.0 below 10.0.0. Other pairs are refused before a job is created. * PHP 7.4.33, 8.0.30, 8.1.34 and 8.2.34 were exercised. Requires PHP 7.4 or newer. -* MySQL 8.0.44 and MariaDB 10.11.15 were exercised, with default and Redis Object Cache 2.7.0 (Redis 7.4.2) persistent cache modes. +* MySQL 8.0.44 and MariaDB 10.11.15 were exercised. Redis Object Cache 2.7.0 (Redis 7.4.2) on the modern line and on 6.7.2 with 9.0.4 at 100 products, not on 6.8.3 with 9.9.7. * WooCommerce refreshes variable parent price ranges after each variation change. * Multisite is unsupported. * Real managed or shared hosting has not been tested. @@ -102,30 +108,24 @@ grant actor - user-0 system cron is outside the grant; (3) human Preview/Approve/Apply and price Undo are unchanged; (4) operators issue, inspect, pause, revoke or remove the restriction with append-only audit. -Protects grant-scoped price/stock jobs, frozen targets, budget, expiry and -pause/resume/revoke/revoke-all/remove restriction. Does not cover firewalls or -sandboxes, administrators outside a grant, hostile plugins, direct SQL, -third-party hook side effects, orders/refunds/coupons/tax/options/arbitrary -meta, stock Undo (Undo is price-only), or any universal "agent-safe" claim. - Can: -* Submit price/stock jobs for the granted actor through the WriteLeash grant route, within frozen scope, budget and expiry. (matrix-integration.php) -* Apply price and stock through the controlled item transaction; variation parent price/stock-status sync inside the frame. (stock-integration.php) -* Operator: issue/inspect/revoke a grant; pause/resume; revoke-all; remove restriction (administrator-only, never self). (grant-admin-integration.php) -* Read job/stock progress and the append-only grant/denial audit. (grant-integration.php) +* Submit price/stock jobs for the granted actor through the WriteLeash grant route, within frozen scope, budget and expiry. +* Apply price and stock through the controlled item transaction; variation parent price/stock-status sync inside the frame. +* Operator: issue/inspect/revoke a grant; pause/resume; revoke-all; remove restriction (administrator-only, never self). +* Read job/stock progress and the append-only grant/denial audit. Cannot: -* Write products through WooCommerce REST, $product->save(), wc_update_product_stock(), direct price/stock meta, trash/delete, outside a matching Execution Frame. (perimeter-integration.php) -* Exceed grant scope, budget, expiry, or the stock bounds (max_abs_delta, floor 0); no stock Undo. (stock-integration.php) -* Un-leash itself; remove its own restriction; operate pause/resume/revoke-all without site administration. (grant-removal-integration.php) -* Cover administrators, hostile plugins, direct SQL, third-party hook side effects, orders/refunds/coupons/tax/options, arbitrary meta, or user-0 system cron. (matrix-integration.php) -* Claim universal "agent-safe" or firewall behavior. (claim-matrix-audit.php) +* Write products through WooCommerce REST, product save, stock helpers, direct price/stock meta, trash or delete, outside a matching Execution Frame. +* Exceed grant scope, budget, expiry, or the stock bounds (max absolute delta, floor 0). No stock Undo. +* Remove its own restriction, or operate pause/resume/revoke-all without site administration. +* Cover administrators, hostile plugins, direct SQL, third-party hook side effects, orders, refunds, coupons, tax, options, arbitrary meta, or user-0 system cron. +* Claim universal agent-safe or firewall behavior. See FREE-GRANT-CONTRACT.md and OPERATION.md. == Installation == -1. Install and activate WooCommerce 10.0 through 11.x. +1. Install and activate WooCommerce on a pair named under Supported scope. 2. Install and activate WriteLeash. 3. Open Products → Bulk Prices. 4. Select products, price field and operation. @@ -140,11 +140,9 @@ See FREE-GRANT-CONTRACT.md and OPERATION.md. 5. History + eligible Undo within 30 days. 6. Safety limits: max products, max % increase/decrease, block zero prices. -Not all features are pictured. - == Frequently Asked Questions == -= Can I bulk edit sale and variation prices? = += Can I change sale and variation prices? = Yes, within scope above. Sale targets must stay below Regular Price; regular targets must remain above an existing sale. @@ -172,6 +170,10 @@ backups. WriteLeash creates no backups. == Changelog == += Unreleased = + +* Listing leads with protection against accidental price and stock overwrites. Supported scope names the tested legacy pairs. No version change. + = 0.2.0 = * Added live read-only Apply and Undo progress without page reloads. From 0f2e0003d9b185b7d0628f1f8bb58ab6bbe8ad55 Mon Sep 17 00:00:00 2001 From: MrDarkRoot Date: Tue, 27 Oct 2026 12:02:56 +0700 Subject: [PATCH 3/6] fix(release,#280): keep the 0.1.0 artifact on WordPress 7.0 The pinned v0.1 builder audits that source with package-preflight and readme-validate. Those checks now expect WordPress 6.7 only for the live package. The reviewed 0.1.0 header stays 7.0, and the pair-rule listing facts stay on the live tree. --- wordpress/tests/release/package-preflight.php | 5 ++++- wordpress/tests/release/readme-validate.php | 14 +++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/wordpress/tests/release/package-preflight.php b/wordpress/tests/release/package-preflight.php index 37e09884..c30ed2b1 100644 --- a/wordpress/tests/release/package-preflight.php +++ b/wordpress/tests/release/package-preflight.php @@ -90,11 +90,14 @@ } // Main file must carry the founder-selected license and the release version. +// The pinned 0.1.0 artifact keeps its reviewed WordPress 7.0 header. The +// live package floor is 6.7 and is not applied backwards to that artifact. $main = (string) file_get_contents( $source . '/writeleash.php' ); $version_pattern = str_replace( '.', '\.', $release ); +$wp_floor_pattern = '0.1.0' === $release ? '7\.0' : '6\.7'; foreach ( array( '/^\s*\*\s*Version:\s*' . $version_pattern . '\s*$/m', - '/^\s*\*\s*Requires at least:\s*6\.7\s*$/m', + '/^\s*\*\s*Requires at least:\s*' . $wp_floor_pattern . '\s*$/m', '/^\s*\*\s*Requires PHP:\s*7\.4\s*$/m', '/^\s*\*\s*License:\s*GPL v2 or later\s*$/m', '/^\s*\*\s*License URI:\s*https:\/\/www\.gnu\.org\/licenses\/gpl-2\.0\.html\s*$/m', diff --git a/wordpress/tests/release/readme-validate.php b/wordpress/tests/release/readme-validate.php index aeb189d7..b976e106 100644 --- a/wordpress/tests/release/readme-validate.php +++ b/wordpress/tests/release/readme-validate.php @@ -25,6 +25,14 @@ } } $text = (string) file_get_contents( $readme ); +$package_version = null; +if ( preg_match( '/^\s*\*\s*Version:\s*(\S+)\s*$/m', (string) file_get_contents( $main ), $package_version_match ) ) { + $package_version = $package_version_match[1]; +} +// build-wordpress-org.py audits the pinned 0.1.0 source with this validator +// and no --frozen-artifact flag. That artifact stays on WordPress 7.0 and the +// WooCommerce 10.0 through 11.x sentence. It is not the pair-rule listing. +$historical_package = ! $frozen && '0.1.0' === $package_version; require_once __DIR__ . '/conflict-copy.php'; writeleash_conflict_copy_audit( $text, $frozen ); if ( strlen( $text ) > 10240 ) { @@ -69,7 +77,7 @@ if ( ! $tags || count( $tags ) > 5 || count( $tags ) !== count( array_unique( $tags ) ) ) { $fail( 'Tags must be 1-5 unique comma-separated terms' ); } -$wp_floor = $frozen ? '7.0' : '6.7'; +$wp_floor = ( $frozen || $historical_package ) ? '7.0' : '6.7'; if ( ! preg_match( '/\A\d+\.\d\z/', $headers['requires at least'] ) || $wp_floor !== $headers['requires at least'] ) { $fail( 'Requires at least must be ' . $wp_floor . ' (live tree: legacy-band floor 6.7; WooCommerce 10.0 through 11.x still requires WordPress 7.0 up to 8.0)' ); } @@ -91,7 +99,7 @@ // Directory metadata and the main plugin header must not contradict each other. $main_header = (string) file_get_contents( $main ); -$header_floor = $frozen ? '7\.0' : '6\.7'; +$header_floor = ( $frozen || $historical_package ) ? '7\.0' : '6\.7'; foreach ( array( '/^\s*\*\s*Requires at least:\s*' . $header_floor . '\s*$/m', '/^\s*\*\s*Requires PHP:\s*7\.4\s*$/m', @@ -137,7 +145,7 @@ $fail( 'readme must name the exact tested WordPress core fixture: ' . $tested_core ); } } -if ( ! $frozen ) { +if ( ! $frozen && ! $historical_package ) { foreach ( array( 'WordPress 7.0 up to 8.0', '6.7.2', '9.0.4', '6.8.3', '9.9.7' ) as $pair_fact ) { if ( ! str_contains( $text, $pair_fact ) ) { $fail( 'readme must keep the modern line and name the tested legacy pairs: ' . $pair_fact ); From d58612c9cf6c0fd541e8bcff5ce8996959022415 Mon Sep 17 00:00:00 2001 From: MrDarkRoot Date: Tue, 27 Oct 2026 12:06:48 +0700 Subject: [PATCH 4/6] fix(release,#280): accept the live pair-rule headers in the v0.2 builder The v0.2 package check was still requiring WordPress 7.0 and WooCommerce 10.0. Those floors now match the reviewed headers, 6.7 and 9.0. The readme cap matches the 10240-byte directory check. Screenshot captions stay exact. --- wordpress/release/CLAIM-MATRIX.md | 2 +- wordpress/release/build-wordpress-org-v02.py | 7 ++++--- wordpress/writeleash/readme.txt | 4 +--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/wordpress/release/CLAIM-MATRIX.md b/wordpress/release/CLAIM-MATRIX.md index 17907c1f..d1054e11 100644 --- a/wordpress/release/CLAIM-MATRIX.md +++ b/wordpress/release/CLAIM-MATRIX.md @@ -120,7 +120,7 @@ Status values: ## 9. Persistent object cache -* **COPY CLAIM:** "MySQL 8.0.44 and MariaDB 10.11.15 were exercised. Redis Object Cache 2.7.0 (Redis 7.4.2) on the modern line and on 6.7.2 with 9.0.4 at 100 products, not on 6.8.3 with 9.9.7." +* **COPY CLAIM:** "MySQL 8.0.44 and MariaDB 10.11.15 were exercised. Redis 7.4.2 with Object Cache 2.7.0: modern line, and 6.7.2 with 9.0.4 at 100 products. Not on 6.8.3 with 9.9.7." * **SUPPORTING ISSUE:** #108 cache verifier; #109/#110 cache suites; #112 cache/hook torture; #280 pair rule. * **SUPPORTING TEST/EVIDENCE:** * `wordpress/tests/acceptance/EVIDENCE.md` — Redis 7.4.2, Redis Object Cache/drop-in 2.7.0 on the modern line; rollback-before-commit, retry, SIGKILL recovery and cache-repair parity passed there. #280 adds Redis on WordPress 6.7.2 + WooCommerce 9.0.4 at size 100, both engines. WordPress 6.8.3 + WooCommerce 9.9.7 persistent cache is NOT_TESTED. diff --git a/wordpress/release/build-wordpress-org-v02.py b/wordpress/release/build-wordpress-org-v02.py index 4458c139..15cbfec5 100644 --- a/wordpress/release/build-wordpress-org-v02.py +++ b/wordpress/release/build-wordpress-org-v02.py @@ -66,9 +66,9 @@ def metadata(payload, version): readme = payload['readme.txt'].decode('utf-8') headers = {'Plugin Name': 'WriteLeash', 'Version': version, 'Author': 'Duy Tran', 'Text Domain': 'writeleash', - 'Requires Plugins': 'woocommerce', 'Requires at least': '7.0', + 'Requires Plugins': 'woocommerce', 'Requires at least': '6.7', 'Tested up to': '7.1', 'Requires PHP': '7.4', - 'WC requires at least': '10.0', 'WC tested up to': '11.1', + 'WC requires at least': '9.0', 'WC tested up to': '11.1', 'License': 'GPL v2 or later'} for key, value in headers.items(): require(re.search(r'^\s*\*\s*' + re.escape(key) + r':\s*' + @@ -79,7 +79,8 @@ def metadata(payload, version): require(re.search(r'^Stable tag:\s*' + re.escape(version) + r'\s*$', readme, re.M), 'stable-tag mismatch') require(re.search(r'^Contributors:\s*duyytrann\s*$', readme, re.M), 'contributor mismatch') - require(len(payload['readme.txt']) < 10000 and '= 0.2.0 =' in readme, + # Same 10 KiB directory guidance as readme-validate.php (10240 bytes). + require(len(payload['readme.txt']) <= 10240 and '= 0.2.0 =' in readme, 'readme size or release notes') require(sum(bool(re.search(rb'^\s*\*?\s*Plugin Name:', d, re.M)) for p, d in payload.items() if p.endswith('.php')) == 1, diff --git a/wordpress/writeleash/readme.txt b/wordpress/writeleash/readme.txt index 5d248876..d04bb6c3 100644 --- a/wordpress/writeleash/readme.txt +++ b/wordpress/writeleash/readme.txt @@ -86,7 +86,7 @@ recorded Regular Price. Availability does not guarantee restoration. * WooCommerce 10.0 through 11.x on WordPress 7.0 up to 8.0 only (10.0.0 or newer, below 12.0.0). * Exercised: WordPress 7.0.1 and 7.1.2 on that line; WordPress 6.7.2 with WooCommerce 9.0.4; WordPress 6.8.3 with WooCommerce 9.9.7. Legacy band is WordPress 6.7 below 7.0 with WooCommerce 9.0.0 below 10.0.0. Other pairs are refused before a job is created. * PHP 7.4.33, 8.0.30, 8.1.34 and 8.2.34 were exercised. Requires PHP 7.4 or newer. -* MySQL 8.0.44 and MariaDB 10.11.15 were exercised. Redis Object Cache 2.7.0 (Redis 7.4.2) on the modern line and on 6.7.2 with 9.0.4 at 100 products, not on 6.8.3 with 9.9.7. +* MySQL 8.0.44 and MariaDB 10.11.15 were exercised. Redis 7.4.2 with Object Cache 2.7.0: modern line, and 6.7.2 with 9.0.4 at 100 products. Not on 6.8.3 with 9.9.7. * WooCommerce refreshes variable parent price ranges after each variation change. * Multisite is unsupported. * Real managed or shared hosting has not been tested. @@ -121,8 +121,6 @@ Cannot: * Cover administrators, hostile plugins, direct SQL, third-party hook side effects, orders, refunds, coupons, tax, options, arbitrary meta, or user-0 system cron. * Claim universal agent-safe or firewall behavior. -See FREE-GRANT-CONTRACT.md and OPERATION.md. - == Installation == 1. Install and activate WooCommerce on a pair named under Supported scope. From 28d65a413416edb69913cae034e265d973848283 Mon Sep 17 00:00:00 2001 From: MrDarkRoot Date: Tue, 27 Oct 2026 12:28:30 +0700 Subject: [PATCH 5/6] test(#280): align admin and journal gates with the pair rule The hermetic admin unit now supplies WordPress 7.1.2, so Resume still reaches the job-id check on the modern pair. WordPress 6.8.3 with WooCommerce 11 is refused before Resume or Undo and starts no worker. A live WordPress version outside both bands fails Apply at the environment gate. The journal case expects WOOCOMMERCE_VERSION_UNSUPPORTED, a PENDING row, and an unchanged price. In-band contract drift stays CONFLICT. --- wordpress/tests/admin/progress-unit.php | 10 ++++++++++ wordpress/tests/durable/integration.php | 12 ++++++++++++ wordpress/tests/durable/worker.php | 2 ++ 3 files changed, 24 insertions(+) diff --git a/wordpress/tests/admin/progress-unit.php b/wordpress/tests/admin/progress-unit.php index 4c05206f..530c2ca8 100644 --- a/wordpress/tests/admin/progress-unit.php +++ b/wordpress/tests/admin/progress-unit.php @@ -4,6 +4,9 @@ require __DIR__ . "/i18n-stubs.php"; define( 'ABSPATH', __DIR__ ); define( 'WC_VERSION', '11.1.2' ); + // Modern certified pair. An empty WordPress version is not a band, so the + // shared dependency gate would refuse Resume and Undo before the job-id regex. + $GLOBALS['wp_version'] = '7.1.2'; $GLOBALS['wl204_actor'] = 7; $GLOBALS['wl204_capable'] = true; $GLOBALS['wl204_http_error'] = null; $GLOBALS['wl204_workers'] = array(); function add_action( $hook, $callback, $priority = 10 ) { $GLOBALS['wl204_hooks'][$hook] = $callback; } function nocache_headers() {} @@ -182,6 +185,13 @@ function refused204() { return array( 'status' => 'FORBIDDEN', 'reason' => 'prog check204( Admin::process_resume( array( 'job' => 'job204', '_wpnonce' => $resume_nonce ), 'POST' ), array( 'status' => 'FORBIDDEN', 'reason' => 'capability_required' ), 'Resume capability re-checked' ); check204( Admin::process_undo( array( 'job' => 'job204', '_wpnonce' => $undo_nonce ), 'POST' ), array( 'status' => 'FORBIDDEN', 'reason' => 'capability_required' ), 'Undo capability re-checked' ); $GLOBALS['wl204_capable'] = true; + // WordPress 6.8 with WooCommerce 11 is the refused cross-band. The refusal + // is the dependency gate, before job-id parsing, and it starts no worker. + $GLOBALS['wp_version'] = '6.8.3'; + check204( Admin::process_resume( array( 'job' => 'job204', '_wpnonce' => $resume_nonce ), 'POST' ), array( 'status' => 'INVALID', 'reason' => 'woocommerce_version_unsupported' ), 'cross-band pair refuses Resume before the job id' ); + check204( Admin::process_undo( array( 'job' => 'job204', '_wpnonce' => $undo_nonce ), 'POST' ), array( 'status' => 'INVALID', 'reason' => 'woocommerce_version_unsupported' ), 'cross-band pair refuses Undo before the job id' ); + check204( $GLOBALS['wl204_workers'], $workers_before, 'cross-band Resume and Undo start no worker' ); + $GLOBALS['wp_version'] = '7.1.2'; check204( Admin::process_resume( array( 'job' => 'bad', '_wpnonce' => $resume_nonce ), 'POST' ), array( 'status' => 'INVALID', 'reason' => 'invalid_job' ), 'Resume job id is regex-checked' ); check204( Admin::process_undo( array( 'job' => 'bad', '_wpnonce' => $undo_nonce ), 'POST' ), array( 'status' => 'INVALID', 'reason' => 'invalid_job' ), 'Undo job id is regex-checked' ); check204( Admin::process_resume( array( 'job' => 'job204', '_wpnonce' => wp_create_nonce( Admin::ACTION_RESUME . '_job205' ) ), 'POST' ), array( 'status' => 'INVALID', 'reason' => 'invalid_nonce' ), 'Resume nonce is job-bound' ); diff --git a/wordpress/tests/durable/integration.php b/wordpress/tests/durable/integration.php index a0b2598b..7bd7dcee 100644 --- a/wordpress/tests/durable/integration.php +++ b/wordpress/tests/durable/integration.php @@ -244,6 +244,18 @@ function edit( array $f, string $value ): void { } elseif ( 'currency' === $drift ) { update_option( 'woocommerce_currency', 'EUR' ); } elseif ( 'decimals' === $drift ) { update_option( 'woocommerce_price_num_decimals', 3 ); } $result = run_item( $f, 'software' === $drift ? array( 'wp_version_fault' => true ) : array() ); + // A live WordPress version outside both certified bands fails the apply + // environment gate before the plan precondition. The item stays PENDING, + // the price stays 100, and no Woo save runs. In-band software drift remains + // a precondition CONFLICT and is covered by the plan integration cases. + if ( 'software' === $drift ) { + eq( $result['code'], 'WOOCOMMERCE_VERSION_UNSUPPORTED', 'live pair outside both bands is an environment refusal' ); + eq( $result['reason'], 'WOOCOMMERCE_VERSION_UNSUPPORTED', 'environment refusal keeps the WooCommerce reason token' ); + eq( hook_count( $f ), 0, 'unsupported pair zero mutation' ); + truth( $f, '100', 'PENDING' ); + marker( 'fresh software pair outside both bands: WOOCOMMERCE_VERSION_UNSUPPORTED / journal PENDING / price unchanged / zero WriteLeash save' ); + continue; + } eq( $result['code'], 'CONFLICT', 'fresh contract drift ' . $drift ); eq( hook_count( $f ), 0, 'contract drift zero mutation' ); update_option( 'woocommerce_currency', 'USD' ); update_option( 'woocommerce_price_num_decimals', 2 ); diff --git a/wordpress/tests/durable/worker.php b/wordpress/tests/durable/worker.php index 34698bc4..5af87d3c 100644 --- a/wordpress/tests/durable/worker.php +++ b/wordpress/tests/durable/worker.php @@ -4,6 +4,8 @@ $fixture = json_decode( file_get_contents( $args[0] ), true ); $plan = unserialize( file_get_contents( $fixture['plan'] ) ); wp_set_current_user( $plan->data()['actor_id'] ); +// 0.0.0 is outside both certified bands. Apply must refuse at the environment +// gate, before a product lock or a precondition conflict. if ( ! empty( $fixture['wp_version_fault'] ) ) { $GLOBALS['wp_version'] = '0.0.0'; } $GLOBALS['wl108_log'] = $fixture['log']; $GLOBALS['wl108_active'] = true; From dc84bce1083418305affa0acb814995c5f5dca65 Mon Sep 17 00:00:00 2001 From: MrDarkRoot Date: Tue, 27 Oct 2026 15:52:41 +0700 Subject: [PATCH 6/6] docs(listing,#322): split public scope and narrow the seatbelt Replace the public Supported scope heading with Requirements, What is supported, and Current limitations. Keep the tested pairs, the pair rule, and the same limitations. The restricted principal is bounded to documented monitored WooCommerce write paths and a matching Execution Frame. Direct SQL, hostile plugins, and unmonitored paths stay uncovered, including when the grant store is unavailable. Stable tag stays 0.2.0. --- wordpress/release/CLAIM-MATRIX.md | 7 ++-- .../tests/release/claim-matrix-audit.php | 4 +- wordpress/tests/release/readme-validate.php | 16 ++++++- wordpress/writeleash/README.md | 2 +- wordpress/writeleash/readme.txt | 42 +++++++++++-------- 5 files changed, 46 insertions(+), 25 deletions(-) diff --git a/wordpress/release/CLAIM-MATRIX.md b/wordpress/release/CLAIM-MATRIX.md index d1054e11..4770c1da 100644 --- a/wordpress/release/CLAIM-MATRIX.md +++ b/wordpress/release/CLAIM-MATRIX.md @@ -242,7 +242,7 @@ Status values: ## 20. Restricted agent access (grant-bound seatbelt) -* **COPY CLAIM:** "A seatbelt, not a firewall: a restricted WordPress principal is bounded to WriteLeash's controlled price/stock job path and fails closed everywhere else. It is not a sandbox or universal 'agent-safe' guarantee." +* **COPY CLAIM:** "A seatbelt, not a firewall: a restricted WordPress principal is bounded to WriteLeash's controlled price and stock job path. Documented monitored WooCommerce write paths are denied outside a matching Execution Frame. Direct SQL, hostile plugins, and unmonitored write paths are not covered. This is not a universal fail-closed guarantee when the grant store is unavailable. It is not a sandbox or universal 'agent-safe' guarantee." * **SUPPORTING ISSUE:** #307, #308, #309, #310, #311, #312. * **SUPPORTING TEST/EVIDENCE:** * `wordpress/tests/jobs/matrix-integration.php` — 28 cells × 4 engine/cache combinations: 15 deny paths, 4 non-interference paths, 4 in-frame positives and 5 grant-state cells. @@ -253,8 +253,8 @@ Status values: * `wordpress/tests/admin/grant-admin-integration.php` — operator matrix, create contract, revoke, remove-restriction warning/audit, pause/resume option posture. * `wordpress/tests/admin/stock-job-integration.php` — stock grant job view, history and read-only progress copy. * `wordpress/tests/admin/grant-cli.sh` — `wp writeleash grant list|create|revoke|disable|enable` with authenticated `--user`, emergency controls administrator-only. -* **ALLOWED WORDING:** The seatbelt sentence above; grant-scoped price/stock jobs through the controlled path; frozen scope, budget, expiry and stock bounds; operator issue/inspect/revoke/pause/resume/revoke-all/remove-restriction; append-only grant/denial audit. -* **REQUIRED LIMITATION:** Not a firewall or sandbox. Does not constrain administrators who are not grant actors, hostile plugins, direct SQL, third-party hook side effects, orders/refunds/coupons/tax/options or arbitrary meta. A user-0 system cron is outside the grant. Stock Undo is unsupported; Undo is price-only. No universal "agent-safe" claim. +* **ALLOWED WORDING:** The seatbelt sentence above; documented monitored WooCommerce write paths denied outside a matching Execution Frame; grant-scoped price/stock jobs through the controlled path; frozen scope, budget, expiry and stock bounds; operator issue/inspect/revoke/pause/resume/revoke-all/remove-restriction; append-only grant/denial audit. +* **REQUIRED LIMITATION:** Not a firewall or sandbox. Do not say the principal fails closed everywhere else. Do not claim universal fail-closed behavior when the grant store is unavailable. Direct SQL, hostile plugins, and unmonitored write paths are not covered. Does not constrain administrators who are not grant actors, third-party hook side effects, orders/refunds/coupons/tax/options or arbitrary meta. A user-0 system cron is outside the grant. Stock Undo is unsupported; Undo is price-only. No universal "agent-safe" claim. * **STATUS:** PUBLISHABLE WITH LIMITATION. ## 21. Accidental price and stock overwrite protection @@ -280,6 +280,7 @@ Status values: * Real managed/shared-host certification. * Universal rollback; "rolls everything back"; "prevents all bad edits"; any protection claim for every Woo write. * Reading the #322 listing as a firewall, hostile-plugin isolation, direct SQL protection, universal agent safety, or protection for every WooCommerce write. Stock wording covers only the opt-in controlled grant path. +* "Fails closed everywhere else," or a universal fail-closed guarantee when the grant store is unavailable. Coverage is the documented monitored WooCommerce write paths and a matching Execution Frame. * Reversal of orders, sales, emails, webhooks, remote HTTP or external queues. * Guaranteed shopper prices; arbitrary save-hook throughput; arbitrary metadata size. * Package-upgrade certification from a released Free package (none exists). diff --git a/wordpress/tests/release/claim-matrix-audit.php b/wordpress/tests/release/claim-matrix-audit.php index 7171e5f6..79000141 100644 --- a/wordpress/tests/release/claim-matrix-audit.php +++ b/wordpress/tests/release/claim-matrix-audit.php @@ -80,14 +80,14 @@ // #312: the restricted-agent boundary must use the same seatbelt wording in // the public listing and the reviewed claim matrix. Whitespace is normalized // so both files may wrap the sentence independently. -$seatbelt = "A seatbelt, not a firewall: a restricted WordPress principal is bounded to WriteLeash's controlled price/stock job path and fails closed everywhere else."; +$seatbelt = "A seatbelt, not a firewall: a restricted WordPress principal is bounded to WriteLeash's controlled price and stock job path. Documented monitored WooCommerce write paths are denied outside a matching Execution Frame. Direct SQL, hostile plugins, and unmonitored write paths are not covered. This is not a universal fail-closed guarantee when the grant store is unavailable."; foreach ( array( 'readme' => $read, 'claim matrix' => $mx ) as $label => $text ) { if ( false === strpos( preg_replace( '/\s+/', ' ', $text ), $seatbelt ) ) { $fail( 'missing exact restricted-agent seatbelt wording in ' . $label ); } } -$unsupported = array( '10,000', '10000', 'universal rollback', 'all bad edits', 'every shared host', 'all WooCommerce versions', 'any WooCommerce version' ); +$unsupported = array( '10,000', '10000', 'universal rollback', 'all bad edits', 'every shared host', 'all WooCommerce versions', 'any WooCommerce version', 'fails closed everywhere else' ); foreach ( $unsupported as $claim ) { if ( false !== stripos( $read, $claim ) ) { $fail( 'unsupported public claim in readme: ' . $claim ); diff --git a/wordpress/tests/release/readme-validate.php b/wordpress/tests/release/readme-validate.php index b976e106..705b8a16 100644 --- a/wordpress/tests/release/readme-validate.php +++ b/wordpress/tests/release/readme-validate.php @@ -122,7 +122,21 @@ } // Required recognizable sections for the WooCommerce Free listing. -foreach ( array( '== Description ==', '== Supported scope ==', '== Installation ==', '== Screenshots ==', '== Frequently Asked Questions ==', '== Changelog ==' ) as $section ) { +// The pinned 0.1.0 and frozen artifacts keep "Supported scope". The live +// listing uses the three #280 headings and must not restore the old heading. +$sections = array( '== Description ==', '== Installation ==', '== Screenshots ==', '== Frequently Asked Questions ==', '== Changelog ==' ); +if ( $frozen || $historical_package ) { + $sections[] = '== Supported scope =='; +} else { + array_push( $sections, '== Requirements ==', '== What is supported ==', '== Current limitations ==' ); + if ( str_contains( $text, '== Supported scope ==' ) ) { + $fail( 'live readme must use Requirements, What is supported, and Current limitations' ); + } + if ( false !== stripos( $text, 'fails closed everywhere else' ) ) { + $fail( 'live readme must not claim the restricted principal fails closed everywhere else' ); + } +} +foreach ( $sections as $section ) { if ( ! str_contains( $text, $section ) ) { $fail( 'missing readme section: ' . $section ); } diff --git a/wordpress/writeleash/README.md b/wordpress/writeleash/README.md index e9272de4..e545fa7f 100644 --- a/wordpress/writeleash/README.md +++ b/wordpress/writeleash/README.md @@ -76,7 +76,7 @@ Requirements, supported versions and the full capability details are in custom database account or manual SQL setup is required. - Plugin entry point: [`writeleash.php`](writeleash.php) -- Supported scope and limitations: [readme.txt](readme.txt) +- Requirements, what is supported, and current limitations: [readme.txt](readme.txt) - License: [GPL-2.0-or-later](LICENSE) WordPress.org approval is founder-reported. Public distribution and a usable diff --git a/wordpress/writeleash/readme.txt b/wordpress/writeleash/readme.txt index d04bb6c3..ec1c8eaa 100644 --- a/wordpress/writeleash/readme.txt +++ b/wordpress/writeleash/readme.txt @@ -45,8 +45,7 @@ endings pass the same safety limits; Clear ignores them. = Choose and exclude = Filter by an inclusive stored Regular or Sale Price range; both endpoints -count, variations use their own prices, and blank sales never match numeric -ranges. Exclude products or variations before approval; confirmed exclusions +count, and blank sales never match. Variations use their own prices. Exclude products or variations before approval; confirmed exclusions stay out of Apply and Undo. = Preview exact changes before Apply = @@ -78,35 +77,43 @@ window. Failed current checks leave the product alone as a conflict. Regular Price Undo preserves later sales where valid; Sale Price Undo checks its recorded Regular Price. Availability does not guarantee restoration. -== Supported scope == +== Requirements == -* Up to 1,000 selected products per new job in the tested configuration. Variations count individually. -* Published core simple products and published variations of published core variable products, in the base store currency. Parent prices are not edited directly. -* Custom product types and universal extension compatibility are unsupported. * WooCommerce 10.0 through 11.x on WordPress 7.0 up to 8.0 only (10.0.0 or newer, below 12.0.0). * Exercised: WordPress 7.0.1 and 7.1.2 on that line; WordPress 6.7.2 with WooCommerce 9.0.4; WordPress 6.8.3 with WooCommerce 9.9.7. Legacy band is WordPress 6.7 below 7.0 with WooCommerce 9.0.0 below 10.0.0. Other pairs are refused before a job is created. * PHP 7.4.33, 8.0.30, 8.1.34 and 8.2.34 were exercised. Requires PHP 7.4 or newer. * MySQL 8.0.44 and MariaDB 10.11.15 were exercised. Redis 7.4.2 with Object Cache 2.7.0: modern line, and 6.7.2 with 9.0.4 at 100 products. Not on 6.8.3 with 9.9.7. + +== What is supported == + +* Up to 1,000 selected products per new job in the tested configuration. Variations count individually. +* Published core simple products and published variations of published core variable products, in the base store currency. Parent prices are not edited directly. * WooCommerce refreshes variable parent price ranges after each variation change. + +== Current limitations == + +* Custom product types and universal extension compatibility are unsupported. * Multisite is unsupported. * Real managed or shared hosting has not been tested. == Restricted agent access == A seatbelt, not a firewall: a restricted WordPress principal is bounded to -WriteLeash's controlled price/stock job path and fails closed everywhere else. +WriteLeash's controlled price and stock job path. Documented monitored +WooCommerce write paths are denied outside a matching Execution Frame. Direct +SQL, hostile plugins, and unmonitored write paths are not covered. This is +not a universal fail-closed guarantee when the grant store is unavailable. It is not a sandbox or universal agent-safe guarantee. Opt-in: restricted access is off until an operator issues a grant; only explicit 0 enables grant-bound writes, and absent or malformed state denies. Who: (1) an AI agent WordPress user (manage_woocommerce + edit_products, human-granted) is the restricted actor; the issuer submits its price/stock -jobs only via POST /wp-json/writeleash/v1/grants/{uuid}/jobs; -direct WooCommerce REST/save/stock/meta/trash/delete is denied outside a -frame; (2) cron, Action Scheduler or WP-CLI --user= running as the -grant actor - user-0 system cron is outside the grant; (3) human -Preview/Approve/Apply and price Undo are unchanged; (4) operators issue, -inspect, pause, revoke or remove the restriction with append-only audit. +jobs only via POST /wp-json/writeleash/v1/grants/{uuid}/jobs; (2) cron, +Action Scheduler or WP-CLI --user= running as the grant actor; +user-0 system cron is outside the grant; (3) human Preview/Approve/Apply +and price Undo are unchanged; (4) operators issue, inspect, pause, revoke +or remove the restriction with an append-only audit. Can: * Submit price/stock jobs for the granted actor through the WriteLeash grant route, within frozen scope, budget and expiry. @@ -118,12 +125,11 @@ Cannot: * Write products through WooCommerce REST, product save, stock helpers, direct price/stock meta, trash or delete, outside a matching Execution Frame. * Exceed grant scope, budget, expiry, or the stock bounds (max absolute delta, floor 0). No stock Undo. * Remove its own restriction, or operate pause/resume/revoke-all without site administration. -* Cover administrators, hostile plugins, direct SQL, third-party hook side effects, orders, refunds, coupons, tax, options, arbitrary meta, or user-0 system cron. -* Claim universal agent-safe or firewall behavior. +* Cover administrators, third-party hook side effects, orders, refunds, coupons, tax, options, arbitrary meta, or user-0 system cron. == Installation == -1. Install and activate WooCommerce on a pair named under Supported scope. +1. Install and activate WooCommerce on a pair named under Requirements. 2. Install and activate WriteLeash. 3. Open Products → Bulk Prices. 4. Select products, price field and operation. @@ -142,7 +148,7 @@ Cannot: = Can I change sale and variation prices? = -Yes, within scope above. Sale targets must stay below Regular Price; regular +Yes, within What is supported above. Sale targets must stay below Regular Price; regular targets must remain above an existing sale. = What happens if a price changes after Preview? = @@ -170,7 +176,7 @@ backups. WriteLeash creates no backups. = Unreleased = -* Listing leads with protection against accidental price and stock overwrites. Supported scope names the tested legacy pairs. No version change. +* Requirements, What is supported, and Current limitations name the tested pairs. No version change. = 0.2.0 =