Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/wordpress-woo-acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: |
Expand Down
79 changes: 48 additions & 31 deletions wordpress/release/CLAIM-MATRIX.md

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions wordpress/release/build-wordpress-org-v02.py
Original file line number Diff line number Diff line change
Expand Up @@ -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*' +
Expand All @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions wordpress/tests/acceptance/EVIDENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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 |
| --- | --- | --- | --- | --- | --- |
| 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.
2 changes: 2 additions & 0 deletions wordpress/tests/acceptance/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 9 additions & 1 deletion wordpress/tests/acceptance/in-container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"
Expand Down
3 changes: 2 additions & 1 deletion wordpress/tests/acceptance/legacy-recovery.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' ); }
Expand Down
6 changes: 5 additions & 1 deletion wordpress/tests/acceptance/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
10 changes: 10 additions & 0 deletions wordpress/tests/admin/progress-unit.php
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand Down Expand Up @@ -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' );
Expand Down
5 changes: 4 additions & 1 deletion wordpress/tests/current-core/in-container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
37 changes: 27 additions & 10 deletions wordpress/tests/dependency-63.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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':
Expand Down
Loading
Loading