This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 93cff09692 [ISSUE #7563] Document wasm host abi and test a gateway 
reference plugin. (#7567)
93cff09692 is described below

commit 93cff096923b2b9041edcd89111e19899923a5de
Author: redyyang <[email protected]>
AuthorDate: Tue Oct 13 11:13:40 2026 +0800

    [ISSUE #7563] Document wasm host abi and test a gateway reference plugin. 
(#7567)
    
    * test(plugin-wasm-base): document host abi and add gateway reference tests.
    
    * fix(ci): use recognizable license headers in wasm gateway fixtures.
    
    ---------
    
    Co-authored-by: redyyang <[email protected]>
---
 .github/scripts/verify-wasm-fixtures.py            | 106 ++++++
 .github/workflows/wasm-fixtures.yml                |  64 ++++
 shenyu-plugin/shenyu-plugin-wasm-api/ABI-v1.md     | 243 ++++++++++++++
 .../shenyu/plugin/wasm/api/loader/WasmLoader.java  |  26 +-
 .../shenyu-plugin-wasm-api/src/test/README.md      |  30 +-
 shenyu-plugin/shenyu-plugin-wasm-base/README.md    | 112 +++++++
 shenyu-plugin/shenyu-plugin-wasm-base/pom.xml      |  30 ++
 .../wasm/base/gateway/ReferenceWasmPlugin.java     | 179 ++++++++++
 .../gateway/ReferenceWasmPluginDataHandler.java    | 130 ++++++++
 .../plugin/wasm/base/gateway/WasmGatewayTest.java  | 364 +++++++++++++++++++++
 .../base/gateway/WasmLifecycleContractTest.java    |  99 ++++++
 ...ugin.wasm.base.gateway.ReferenceWasmPlugin.wasm | Bin 0 -> 358 bytes
 ...ase.gateway.ReferenceWasmPluginDataHandler.wasm | Bin 0 -> 293 bytes
 ...y.WasmGatewayTest$IncompatibleResultPlugin.wasm | Bin 0 -> 107 bytes
 ...ateway.WasmGatewayTest$InvalidMemoryPlugin.wasm | Bin 0 -> 95 bytes
 ...teway.WasmGatewayTest$MissingExecutePlugin.wasm | Bin 0 -> 25 bytes
 ...ateway.WasmGatewayTest$MissingHooksHandler.wasm | Bin 0 -> 25 bytes
 ...ase.gateway.WasmGatewayTest$TrappingPlugin.wasm | Bin 0 -> 94 bytes
 ...asmLifecycleContractTest$InvalidHookLoader.wasm | Bin 0 -> 42 bytes
 ...cycleContractTest$InvalidInitializerLoader.wasm | Bin 0 -> 47 bytes
 ...ecycleContractTest$MissingDiscoveryHandler.wasm | Bin 0 -> 25 bytes
 ...fecycleContractTest$MissingMetadataHandler.wasm | Bin 0 -> 25 bytes
 .../src/test/wasm-gateway/Makefile                 |  36 ++
 .../test/wasm-gateway/configuration-handler.wat    |  34 ++
 .../src/test/wasm-gateway/incompatible-result.wat  |  24 ++
 .../src/test/wasm-gateway/invalid-hook.wat         |  20 ++
 .../src/test/wasm-gateway/invalid-initializer.wat  |  20 ++
 .../src/test/wasm-gateway/invalid-memory.wat       |  22 ++
 .../src/test/wasm-gateway/missing-hooks.wat        |  18 +
 .../src/test/wasm-gateway/reference-plugin.wat     |  72 ++++
 .../src/test/wasm-gateway/trap.wat                 |  23 ++
 31 files changed, 1638 insertions(+), 14 deletions(-)

diff --git a/.github/scripts/verify-wasm-fixtures.py 
b/.github/scripts/verify-wasm-fixtures.py
new file mode 100644
index 0000000000..9e8cd729a5
--- /dev/null
+++ b/.github/scripts/verify-wasm-fixtures.py
@@ -0,0 +1,106 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Rebuild WAT fixtures exactly and test freshly compiled legacy Rust 
guests."""
+
+import argparse
+import shutil
+import subprocess
+import tempfile
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[2]
+PLUGIN = ROOT / "shenyu-plugin"
+API = PLUGIN / "shenyu-plugin-wasm-api" / "src/test"
+BASE = PLUGIN / "shenyu-plugin-wasm-base" / "src/test"
+RUST_TOOLCHAIN = "1.90.0"
+RUST_GUESTS = [
+    (API, "rust-wasm-plugin", 
"org.apache.shenyu.plugin.wasm.api.AbstractWasmPluginTest$RustWasmPlugin.wasm"),
+    (BASE, "rust-shenyu-wasm-plugin", 
"org.apache.shenyu.plugin.wasm.base.AbstractShenyuWasmPluginTest$TestShenyuWasmPlugin.wasm"),
+    (BASE, "rust-plugin-data-handler", 
"org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmPluginDataHandlerTest$TestWasmPluginDataHandler.wasm"),
+    (BASE, "rust-meta-data-handler", 
"org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmMetaDataHandlerTest$TestWasmMetaDataHandler.wasm"),
+    (BASE, "rust-discovery-handler", 
"org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmDiscoveryHandlerTest$TestWasmPluginDiscoveryHandler.wasm"),
+]
+
+
+def run(command, **kwargs):
+    subprocess.run(command, check=True, **kwargs)
+
+
+def verify_wat():
+    version = subprocess.check_output(["wat2wasm", "--version"], 
text=True).strip()
+    if version != "1.0.42":
+        raise RuntimeError("WAT verification requires WABT 1.0.42, found " + 
version)
+    count = 0
+    for family in ["wasm-bridge", "wasm-gateway"]:
+        directory = BASE / family
+        if not directory.exists():
+            continue
+        with tempfile.TemporaryDirectory(prefix="shenyu-wat-") as temporary:
+            run(["make", "-C", str(directory), "build", "RESOURCES=" + 
temporary])
+            binaries = list(Path(temporary).glob("*.wasm"))
+            if not binaries:
+                raise RuntimeError("No fixtures generated for " + family)
+            for rebuilt in binaries:
+                committed = BASE / "resources" / rebuilt.name
+                if rebuilt.read_bytes() != committed.read_bytes():
+                    raise RuntimeError("WAT fixture differs from committed 
binary: " + rebuilt.name)
+                count += 1
+    if not count:
+        raise RuntimeError("No WAT fixture families found")
+    print("Verified " + str(count) + " byte-identical WAT fixtures", 
flush=True)
+
+
+def verify_rust():
+    originals = {}
+    try:
+        with tempfile.TemporaryDirectory(prefix="shenyu-rust-") as temporary:
+            for source_root, crate, resource_name in RUST_GUESTS:
+                source = source_root / crate
+                copied = Path(temporary) / crate
+                copied.mkdir()
+                shutil.copyfile(source / "Cargo.toml", copied / "Cargo.toml")
+                shutil.copytree(source / "src", copied / "src")
+                run(["cargo", "+" + RUST_TOOLCHAIN, "build", 
"--manifest-path", str(copied / "Cargo.toml"),
+                     "--target", "wasm32-wasip1", "--release", "--offline"])
+                binary = copied / "target/wasm32-wasip1/release" / 
(crate.replace("-", "_") + ".wasm")
+                resource = source_root / "resources" / resource_name
+                originals[resource] = resource.read_bytes()
+                shutil.copyfile(binary, resource)
+            # Maven copies the freshly rebuilt guests before the tests load 
them.
+            # Legacy compiler versions were not recorded, so assert behavior 
instead of bytes.
+            run(["./mvnw", "-B", "-ntp", "-pl", 
"shenyu-plugin/shenyu-plugin-wasm-base", "-am", "test",
+                 "-Dtest=*Wasm*Test", 
"-Dsurefire.failIfNoSpecifiedTests=false",
+                 "-DskipRemoteResources=true", "-Djacoco.skip=true", 
"-Dmaven.javadoc.skip=true"], cwd=ROOT)
+    finally:
+        for resource, original in originals.items():
+            resource.write_bytes(original)
+    print("Verified five rebuilt Rust guests through the WASM Java suites", 
flush=True)
+
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+    mode = parser.add_mutually_exclusive_group()
+    mode.add_argument("--wat-only", action="store_true")
+    mode.add_argument("--rust-only", action="store_true")
+    options = parser.parse_args()
+    if not options.rust_only:
+        verify_wat()
+    if not options.wat_only:
+        verify_rust()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/.github/workflows/wasm-fixtures.yml 
b/.github/workflows/wasm-fixtures.yml
new file mode 100644
index 0000000000..0923070470
--- /dev/null
+++ b/.github/workflows/wasm-fixtures.yml
@@ -0,0 +1,64 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: wasm-fixtures
+
+on:
+  pull_request:
+    paths:
+      - 'shenyu-plugin/shenyu-plugin-wasm-api/**'
+      - 'shenyu-plugin/shenyu-plugin-wasm-base/**'
+      - '.github/scripts/verify-wasm-fixtures.py'
+      - '.github/workflows/wasm-fixtures.yml'
+      - 'pom.xml'
+  push:
+    branches: [master]
+    paths:
+      - 'shenyu-plugin/shenyu-plugin-wasm-api/**'
+      - 'shenyu-plugin/shenyu-plugin-wasm-base/**'
+      - '.github/scripts/verify-wasm-fixtures.py'
+      - '.github/workflows/wasm-fixtures.yml'
+      - 'pom.xml'
+
+permissions:
+  contents: read
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || 
github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  rebuild-and-test:
+    runs-on: ubuntu-latest
+    timeout-minutes: 30
+    steps:
+      - uses: actions/checkout@v3
+      - uses: ./actions/setup-java-with-retry
+        with:
+          java-version: "17"
+          distribution: "temurin"
+      - name: Install pinned WABT
+        shell: bash
+        run: |
+          curl --fail --location --retry 3 \
+            
https://github.com/WebAssembly/wabt/releases/download/1.0.42/wabt-1.0.42-linux-x64.tar.gz
 \
+            --output "${RUNNER_TEMP}/wabt.tar.gz"
+          echo 
"84895407a6bbb80e918f33b16b2fb2206021c150b6bc9ff6f761263a745ab131  
${RUNNER_TEMP}/wabt.tar.gz" | sha256sum --check
+          tar -xzf "${RUNNER_TEMP}/wabt.tar.gz" -C "${RUNNER_TEMP}"
+          echo "${RUNNER_TEMP}/wabt-1.0.42/bin" >> "${GITHUB_PATH}"
+      - name: Install pinned Rust and WASI target
+        run: rustup toolchain install 1.90.0 --profile minimal --target 
wasm32-wasip1
+      - name: Check WAT binaries and test rebuilt Rust guests
+        run: python3 .github/scripts/verify-wasm-fixtures.py
diff --git a/shenyu-plugin/shenyu-plugin-wasm-api/ABI-v1.md 
b/shenyu-plugin/shenyu-plugin-wasm-api/ABI-v1.md
new file mode 100644
index 0000000000..b19989ed22
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-wasm-api/ABI-v1.md
@@ -0,0 +1,243 @@
+# ShenYu WASM host ABI, version 1
+
+This is the versioned contract for `shenyu-plugin-wasm-api` and
+`shenyu-plugin-wasm-base` in ShenYu `2.7.2-SNAPSHOT` with Java 17 and Chicory
+1.7.3. **Host ABI v1** names the existing transport and lifecycle contract;
+**reference request profile v1** below names one concrete serialization and
+error-code policy. They are separate contracts: the abstract runtime does not
+serialize HTTP exchanges or configuration DTOs automatically.
+
+## Loading and initialization
+
+A Java subclass is still needed to choose the plugin name, order, argument IDs,
+serialization, host callbacks and chain behavior. There is no standalone 
starter
+for these abstract libraries. Register a concrete `ShenyuPlugin` bean with the
+gateway; register its `PluginDataHandler` bean if configuration hooks are 
needed.
+
+The resource name is the concrete Java class's fully qualified binary name plus
+`.wasm`, at the classpath root. For a nested class the name contains `$`, not a
+package directory or a dot between enclosing and nested names. Each loader owns
+one Chicory instance and its linear memory. It tries the runtime compiler first
+and falls back to the interpreter when compilation/instantiation fails.
+
+The loader registers `wasi_snapshot_preview1` imports, then the `shenyu` 
imports,
+and instantiates with automatic start disabled. If `_initialize: () -> ()` is
+exported, it is called once during construction (needed by the TinyGo 
fixtures).
+Neither `_start` nor `main` is called. Linear memory must be available. An 
absent
+resource or unavailable memory prevents initialization; malformed modules,
+unresolved imports, invalid export types and initialization traps propagate as
+initialization failures rather than being treated as missing optional hooks.
+
+WASI stdout/stderr are attached to the Java process. Environment, arguments and
+filesystem access are not inherited. The loader overrides `proc_exit(i32)`:
+zero returns normally, while nonzero throws `ShenyuWasmException`. Returning 
from
+this import is ShenYu-specific; guests must not assume it terminates execution.
+
+## Imports and memory ownership
+
+The namespace is exactly `shenyu` (case sensitive). Both imports have this Wasm
+signature, including the **i64 pointer** even on wasm32:
+
+```wat
+(import "shenyu" "get_args"   (func (param i64 i64 i32) (result i32)))
+(import "shenyu" "put_result" (func (param i64 i64 i32) (result i32)))
+```
+
+| Parameter | Meaning |
+| --- | --- |
+| `argId: i64` | Opaque Java-side invocation ID; not an object address. |
+| `addr: i64` | Byte offset into the calling instance's linear memory. wasm32 
guests widen their pointer to i64. |
+| `len: i32` | Buffer capacity for `get_args`, exact payload byte count for 
`put_result`. |
+
+`get_args` lets the host write serialized arguments into guest-owned memory.
+Its conventional success result is the number of bytes written; zero means no
+data. `put_result` lets the host copy/process bytes from guest-owned memory;
+its conventional success result is zero, with nonzero indicating an adapter
+error. The guest must keep the referenced bytes valid until the import returns.
+The host must copy any bytes it needs after the callback. Lengths count bytes,
+not characters; strings need an explicitly agreed encoding and are not
+implicitly NUL-terminated.
+
+Validate nonnegative lengths and the complete `addr..addr+len` range before
+accessing memory. Do not silently truncate i64 addresses to Java int values.
+The reference adapter uses `Math.toIntExact` and Chicory's checked memory 
access;
+it does not support offsets beyond Java's signed int range. Address zero is a
+valid memory offset, not a null pointer by itself. A zero-length payload needs
+an adapter policy; the runtime does not impose one.
+
+### Serialization, short buffers and codes
+
+`WasmLoader`'s built-in callbacks are **no-ops returning zero**. Override
+`onGetArgs`/`onPutResult` (for loader subclasses), or register imports with the
+`Consumer<Store>` constructor of `AbstractShenyuWasmPlugin`. A non-null 
consumer
+replaces both built-in `shenyu` imports and skips `initWasmCallJavaFunc`; it 
does
+not replace the WASI imports. Host callbacks can be registered during
+construction, so they must not require subclass fields before those fields have
+been initialized.
+
+Host ABI v1 has no universal JSON schema, short-buffer retry protocol or error
+number registry. Existing Rust/TinyGo unit adapters use small UTF-8 strings and
+may copy `min(capacity, payload size)`. Their behavior does not establish a
+lossless production protocol. Every concrete adapter must document its payload,
+short-buffer behavior, return codes and result validation. The reference 
profile
+below specifies and tests all of these. Negative codes are ordinary i32 import
+results; the loader does not automatically turn them into exceptions or HTTP
+responses. A guest must check them. Likewise, Java callers do not interpret a
+lifecycle export's return value as a chain decision: use `put_result` and the
+Java continuation method.
+
+## Lifecycle exports
+
+All names are case sensitive. The fixtures use `(i64) -> ()` for ID-bearing
+exports and `() -> ()` for `refresh` and `_initialize`. Extra return values are
+not interpreted by the templates; adapters should use the documented 
signatures.
+
+| Java abstraction | Export | Required when invoked | Missing-export behavior |
+| --- | --- | --- | --- |
+| `AbstractWasmPlugin` | `execute(i64)` | Yes | HTTP 500, ShenYu result code 
533 (`WASM_FUNC_NOT_FOUND`); no continuation. |
+| `AbstractShenyuWasmPlugin` | `doExecute(i64)` | Yes, after selector/rule 
matching | Same HTTP 500/result code 533; no continuation. |
+| Either request plugin | `before(i64)`, `after(i64)` | No | No-op. |
+| `AbstractWasmPluginDataHandler` | `handlerPlugin(i64)`, `removePlugin(i64)` 
| No | No-op. |
+| Same data handler | `handlerSelector(i64)`, `removeSelector(i64)` | No | 
No-op. |
+| Same data handler | `handlerRule(i64)`, `removeRule(i64)` | No | No-op. |
+| `AbstractWasmMetaDataHandler` | `handleMetaData(i64)`, `removeMetaData(i64)` 
| Yes, for the corresponding operation | `ShenyuWasmInitException`. |
+| Same metadata handler | `refresh()` | No | No-op. |
+| `AbstractWasmDiscoveryHandler` | `handlerDiscoveryUpstreamData(i64)` | Yes | 
`ShenyuWasmInitException`. |
+| Any loader | `_initialize()` | No | No-op during loading. |
+
+An export with a matching name but the wrong kind (for example a global instead
+of a function) is invalid, not an absent optional hook.
+
+Request invocation arguments include the exchange and chain. The matched base
+plugin also supplies `SelectorData` and `RuleData`. `before`/`after` receive an
+exchange with null chain/selector/rule. Plugin data hooks receive their DTO;
+metadata/discovery hooks receive their corresponding DTO. `refresh` has no
+invocation ID and does not populate an argument map. In particular, the legacy
+metadata fixture's use of ID zero during refresh is its own adapter convention.
+
+Java publishes an argument immediately before entering the guest and removes it
+in `finally`, including on traps or callback failures. It has already been
+removed when the Java `doExecute(..., argumentId)` continuation runs. Copy the
+result into request-owned storage during `put_result`; retaining just the ID
+cannot retrieve the argument later. IDs must be unique across concurrent
+invocations sharing the static argument maps, including plugin instances.
+The default plugin-data ID methods return zero and must be overridden for
+concurrent use.
+
+The gateway's `before`/`after` calls surround construction of the plugin's
+reactive publisher, not completion of the asynchronous HTTP response. Guests
+must not use `after` as an asynchronous response-completion hook.
+
+A concurrent argument map does not make a Chicory instance or its linear memory
+safe for overlapping calls. Serialize access to a shared instance, including
+its callbacks and result copying, or use isolated instances. The reference
+plugin serializes synchronous guest execution and stores its copied decision on
+the exchange; its data handler serializes all six configuration operations.
+The abstract libraries do not provide scheduling, pooling, execution deadlines
+or automatic retries.
+
+## Reference request profile v1
+
+The [gateway reference 
test](../shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/WasmGatewayTest.java)
+bootstraps a Spring Boot WebFlux server with the same
+`shenyu-spring-boot-starter-gateway` used by `shenyu-bootstrap`. Its concrete
+[Java 
plugin](../shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/ReferenceWasmPlugin.java)
+and [configuration 
handler](../shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/ReferenceWasmPluginDataHandler.java)
+are Spring beans discovered by the real gateway configuration. HTTP requests
+travel through `ShenyuWebHandler`; configuration enters through the real
+`PluginDataSubscriber`, matching cache and guest configuration hooks.
+
+The [WAT 
guest](../shenyu-plugin-wasm-base/src/test/wasm-gateway/reference-plugin.wat)
+allows a request when `X-Token` equals the rule handle **and** the selector 
handle
+is a prefix of the request path. The guest decides; Java translates the copied
+decision to either `chain.execute(exchange)` or HTTP 403. A terminal Java 
plugin
+adds `X-Chain-Reached: true` and responds with the guest's echoed fields. 
Denied
+requests have no chain header. Unmatched/disabled/deleted configurations bypass
+the WASM execution and continue normally. This example is a test reference,
+not a production authentication plugin.
+
+### Request frame
+
+All integers are four-byte little-endian values. The frame starts with version
+`1`, followed by ten fields. Each field is `byteLength: i32` followed by that
+many UTF-8 bytes, with no terminator or padding:
+
+1. selector ID
+2. selector handle (path prefix)
+3. rule ID
+4. rule handle (accepted token)
+5. HTTP method
+6. raw request path
+7. raw query, or empty string
+8. `X-Token`, or empty string
+9. `X-Request-Id`, or empty string
+10. UTF-8 request body, or empty string
+
+The guest owns one 64-KiB memory page. It reads the frame at offset 16 and 
leaves
+four bytes at offset 12 for its decision. The adapter buffers at most 32 KiB of
+HTTP body for this example; a production implementation must specify its own
+body limits, replay and streaming behavior. The terminal example consumes the
+copied frame and does not forward the body to an upstream service.
+
+| Import outcome | Reference code | Behavior |
+| --- | --- | --- |
+| `get_args` success | Positive payload byte count | Full frame written. |
+| No argument for this ID | `-2` | No memory write. |
+| Buffer too small | `-1` | No partial write; guest retries with its full 
65520-byte buffer. |
+| `put_result` success | `0` | Copy and decode result onto the exchange. |
+| `put_result` unknown ID | `-2` | No result accepted. |
+| Invalid memory, version, decision or frame | Exception/trap | Request fails; 
no chain continuation; argument removed. |
+
+The guest deliberately starts with capacity one and requires `-1` before
+retrying. A frame still too large for the full buffer fails the guest 
invocation;
+there is no dynamic memory growth or truncation. The result is a decision i32
+(`0` allow, `1` deny) followed by the original request frame. Version and field
+lengths are validated by the Java decoder; malformed results fail the request.
+
+### Configuration frame
+
+The handler's `get_args` returns the complete UTF-8 Gson serialization of the
+`PluginData`, `SelectorData` or `RuleData` DTO, with the same `-1`/`-2` policy.
+The configuration guest acknowledges the unchanged JSON through `put_result`,
+prefixed with an i32 event: `0` plugin update, `1` selector update, `2` rule 
update,
+`3` plugin delete, `4` selector delete, `5` rule delete. Tests compare the 
actual
+guest acknowledgement with the sent DTO and then verify subsequent HTTP 
behavior.
+The handler and request guest are separate instances; the Java matching cache
+delivers the latest matched handles to the request guest. This profile does not
+promise stable DTO JSON across future ShenYu versions.
+
+## Compatibility and tested toolchains
+
+Host ABI v1 is documented, not negotiated on the wire: there is no automatic
+`abi_version` import/export or loader rejection based on this document's 
version.
+An adapter must select compatible guests. Changing import types/names, pointer
+widths, required exports or payload/error semantics requires a new version of
+the affected contract; optional exports may be added without making older 
guests
+implement them. A plugin-specific payload change needs a profile version 
change,
+not necessarily a new host ABI. The reference frame's version is checked by 
both
+sides, so it must stay at 1 unless both adapter and guest are updated.
+
+| Fixture family | Target/build | What tests establish |
+| --- | --- | --- |
+| Existing Rust fixtures | Rust edition 2021 `cdylib`, `cargo build --target 
wasm32-wasip1 --release` | WASI preview1, imports, lifecycle and string 
communication for the checked-in binaries. |
+| Existing Go fixtures | **TinyGo**, `tinygo build -target wasm-unknown -opt=2 
-no-debug -panic=trap` | TinyGo library exports and `_initialize` for the 
checked-in binaries. |
+| Gateway reference and failure fixtures | WABT `wat2wasm` 1.0.42, wasm32 | 
Exact ABI types, no optional hooks, short-buffer handling, 
HTTP/configuration/chain integration, concurrency, traps and bad memory. |
+
+The older Rust/TinyGo binaries do not record exact compiler versions. Their
+source build commands are documented below, but byte-for-byte reproduction of
+those legacy binaries is not claimed. The Go `go.mod` language version alone is
+not evidence of support for arbitrary Go runtimes. In particular, standard Go
+`GOOS=wasip1 GOARCH=wasm`, JavaScript `GOOS=js`, Go schedulers and all WASI 
features
+are not covered by these tests. The helper under
+`shenyu-plugin-wasm-base/src/test/pkg/wasmabi` is for internal tests, not a
+published SDK; it clamps large `get_args` counts and discards `put_result` 
codes,
+so it must not be assumed to implement the reference profile's error handling.
+
+## Reproduce the fixtures and tests
+
+The [WASM base README](../shenyu-plugin-wasm-base/README.md) lists the gateway
+command and fixture builds. Tests load checked-in `.wasm` files and need Java 
17
+only; Rust, TinyGo and WABT are only needed to rebuild fixtures. No Docker,
+ShenYu Admin, database or external upstream is required. Configuration is sent
+directly to the gateway-side subscriber, so admin persistence and network data
+sync transports are outside the test's scope.
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-api/src/main/java/org/apache/shenyu/plugin/wasm/api/loader/WasmLoader.java
 
b/shenyu-plugin/shenyu-plugin-wasm-api/src/main/java/org/apache/shenyu/plugin/wasm/api/loader/WasmLoader.java
index e3f1f7140b..14fcf64b07 100644
--- 
a/shenyu-plugin/shenyu-plugin-wasm-api/src/main/java/org/apache/shenyu/plugin/wasm/api/loader/WasmLoader.java
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-api/src/main/java/org/apache/shenyu/plugin/wasm/api/loader/WasmLoader.java
@@ -25,7 +25,6 @@ import com.dylibso.chicory.runtime.Memory;
 import com.dylibso.chicory.runtime.Store;
 import com.dylibso.chicory.wasm.Parser;
 import com.dylibso.chicory.wasm.WasmModule;
-import com.dylibso.chicory.wasm.types.ExportSection;
 import com.dylibso.chicory.wasm.types.FunctionType;
 import com.dylibso.chicory.wasm.types.ValType;
 import org.apache.shenyu.plugin.wasm.api.exception.ShenyuWasmException;
@@ -137,15 +136,10 @@ public class WasmLoader implements AutoCloseable {
             }
 
             // Call _initialize if present (required by TinyGo -target 
wasm-unknown).
-            try {
-                ExportFunction initFn = instance.export("_initialize");
-                if (Objects.nonNull(initFn)) {
-                    initFn.apply();
-                    LOG.debug("Called _initialize for {}", wasmName);
-                }
-            } catch (com.dylibso.chicory.wasm.InvalidException e) {
-                LOG.debug("No _initialize export in {}", wasmName);
-            }
+            findWasmExtern("_initialize").ifPresent(initFn -> {
+                initFn.apply();
+                LOG.debug("Called _initialize for {}", wasmName);
+            });
 
             if (Objects.isNull(instance.memory())) {
                 throw new ShenyuWasmInitException("memory not available in 
wasm file: " + wasmName);
@@ -276,9 +270,15 @@ public class WasmLoader implements AutoCloseable {
      * @return an Optional containing the ExportFunction if found, otherwise 
empty
      */
     public Optional<ExportFunction> getWasmExtern(final String funcName) {
-        ExportSection exports = instance.module().exportSection();
-        for (int index = 0; index < exports.exportCount(); index++) {
-            if (funcName.equals(exports.getExport(index).name())) {
+        return findWasmExtern(funcName);
+    }
+
+    private Optional<ExportFunction> findWasmExtern(final String funcName) {
+        // Chicory throws InvalidException for an absent export. Check the 
names first
+        // so optional hooks remain optional without hiding invalid export 
types.
+        var exports = instance.module().exportSection();
+        for (int i = 0; i < exports.exportCount(); i++) {
+            if (exports.getExport(i).name().equals(funcName)) {
                 return Optional.of(instance.export(funcName));
             }
         }
diff --git a/shenyu-plugin/shenyu-plugin-wasm-api/src/test/README.md 
b/shenyu-plugin/shenyu-plugin-wasm-api/src/test/README.md
index 645154bb2d..0118d480c8 100644
--- a/shenyu-plugin/shenyu-plugin-wasm-api/src/test/README.md
+++ b/shenyu-plugin/shenyu-plugin-wasm-api/src/test/README.md
@@ -1,4 +1,10 @@
-# How to build the wasm file
+# WASM API fixtures
+
+See the versioned [host ABI v1](../../ABI-v1.md) and the
+[real gateway reference test](../../../shenyu-plugin-wasm-base/README.md).
+These are fixture builds, not a statement of support for arbitrary Go runtimes.
+
+## Build the Rust fixture
 
 1. install rustup
 
@@ -17,3 +23,25 @@ in 
`{shenyu}/shenyu-plugin/shenyu-plugin-wasm-api/src/test/rust-wasm-plugin/targ
 4. rename the wasm file
 
 rename the file to 
`org.apache.shenyu.plugin.wasm.api.AbstractWasmPluginTest$RustWasmPlugin.wasm`
+
+
+From the repository root, the complete Rust build and copy command is:
+
+```sh
+rustup target add wasm32-wasip1
+api=shenyu-plugin/shenyu-plugin-wasm-api/src/test
+cargo build --manifest-path "$api/rust-wasm-plugin/Cargo.toml" --target 
wasm32-wasip1 --release
+cp "$api/rust-wasm-plugin/target/wasm32-wasip1/release/rust_wasm_plugin.wasm" \
+  
"$api/resources/org.apache.shenyu.plugin.wasm.api.AbstractWasmPluginTest\$RustWasmPlugin.wasm"
+```
+
+## Build the TinyGo fixture
+
+```sh
+make -C shenyu-plugin/shenyu-plugin-wasm-api/src/test/go-wasm-plugin build
+```
+
+This uses TinyGo `-target wasm-unknown -opt=2 -no-debug -panic=trap`. The exact
+Rust/TinyGo compiler versions used for the older checked-in binaries were not
+recorded. Run both module suites using the command in the WASM base README 
after
+rebuilding.
diff --git a/shenyu-plugin/shenyu-plugin-wasm-base/README.md 
b/shenyu-plugin/shenyu-plugin-wasm-base/README.md
index 84c35ee41f..7ed43b47b5 100644
--- a/shenyu-plugin/shenyu-plugin-wasm-base/README.md
+++ b/shenyu-plugin/shenyu-plugin-wasm-base/README.md
@@ -1,3 +1,95 @@
+# WASM base and gateway reference tests
+
+Read the versioned [host ABI v1 and reference profile 
v1](../shenyu-plugin-wasm-api/ABI-v1.md)
+before implementing a guest or Java adapter.
+
+## Run the gateway test
+
+From the repository root, with Java 17:
+
+```sh
+./mvnw -B -ntp -pl shenyu-plugin/shenyu-plugin-wasm-base -am test \
+  -Dtest=WasmGatewayTest -Dsurefire.failIfNoSpecifiedTests=false \
+  -DskipRemoteResources=true -Djacoco.skip=true
+```
+
+This starts an embedded gateway on a random TCP port using the gateway starter,
+registers the reference plugin and data handler as Spring beans, and sends real
+HTTP requests. It verifies selector/rule handles, method, path, query, headers,
+UTF-8 body, a guest allow/deny decision, chain continuation/short circuit,
+configuration updates/deletes, unmatched requests, 60 concurrent requests,
+missing exports, guest traps and invalid memory. Every invocation must release
+its Java argument, including failure paths. Failure tests replace the guest 
with
+real compiled Wasm fixtures and then verify healthy requests still succeed.
+
+To run both existing Rust/TinyGo unit suites and the gateway test:
+
+```sh
+./mvnw -B -ntp -pl 
shenyu-plugin/shenyu-plugin-wasm-api,shenyu-plugin/shenyu-plugin-wasm-base \
+  -am test -Dtest='*Wasm*Test' -Dsurefire.failIfNoSpecifiedTests=false \
+  -DskipRemoteResources=true -Djacoco.skip=true
+```
+
+The gateway tests are normal Surefire tests and therefore run in module CI.
+Dependencies on Spring Boot and the gateway starter are test-scoped.
+
+## Rebuild the gateway Wasm fixtures
+
+Install WABT 1.0.42 (`wat2wasm --version`). From the repository root:
+
+```sh
+make -C shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway build
+```
+
+The Makefile maps each WAT source to the fully qualified Java resource name,
+including quoted `$` characters for nested test classes. The binaries are
+checked in so Java builds do not need a Wasm compiler. Rebuilding with the 
stated
+WABT version should leave `git diff` empty for these binaries.
+
+## Rebuild the existing Rust fixtures
+
+Install Rust and the `wasm32-wasip1` target:
+
+```sh
+rustup target add wasm32-wasip1
+```
+
+From the repository root, build and copy the base fixtures:
+
+```sh
+base=shenyu-plugin/shenyu-plugin-wasm-base/src/test
+cargo build --manifest-path "$base/rust-shenyu-wasm-plugin/Cargo.toml" 
--target wasm32-wasip1 --release
+cp 
"$base/rust-shenyu-wasm-plugin/target/wasm32-wasip1/release/rust_shenyu_wasm_plugin.wasm"
 \
+  
"$base/resources/org.apache.shenyu.plugin.wasm.base.AbstractShenyuWasmPluginTest\$TestShenyuWasmPlugin.wasm"
+cargo build --manifest-path "$base/rust-plugin-data-handler/Cargo.toml" 
--target wasm32-wasip1 --release
+cp 
"$base/rust-plugin-data-handler/target/wasm32-wasip1/release/rust_plugin_data_handler.wasm"
 \
+  
"$base/resources/org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmPluginDataHandlerTest\$TestWasmPluginDataHandler.wasm"
+cargo build --manifest-path "$base/rust-meta-data-handler/Cargo.toml" --target 
wasm32-wasip1 --release
+cp 
"$base/rust-meta-data-handler/target/wasm32-wasip1/release/rust_meta_data_handler.wasm"
 \
+  
"$base/resources/org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmMetaDataHandlerTest\$TestWasmMetaDataHandler.wasm"
+cargo build --manifest-path "$base/rust-discovery-handler/Cargo.toml" --target 
wasm32-wasip1 --release
+cp 
"$base/rust-discovery-handler/target/wasm32-wasip1/release/rust_discovery_handler.wasm"
 \
+  
"$base/resources/org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmDiscoveryHandlerTest\$TestWasmPluginDiscoveryHandler.wasm"
+```
+
+The exact compiler versions of the legacy binaries were not recorded. Rebuilds
+may differ byte-for-byte; run the Java tests to verify the rebuilt artifacts.
+
+## Rebuild the existing TinyGo fixtures
+
+With TinyGo installed, from the repository root:
+
+```sh
+make -C shenyu-plugin/shenyu-plugin-wasm-base/src/test/go-shenyu-wasm-plugin 
build
+make -C shenyu-plugin/shenyu-plugin-wasm-base/src/test/go-plugin-data-handler 
build
+make -C shenyu-plugin/shenyu-plugin-wasm-base/src/test/go-meta-data-handler 
build
+make -C shenyu-plugin/shenyu-plugin-wasm-base/src/test/go-discovery-handler 
build
+```
+
+These Makefiles build with `-target wasm-unknown -opt=2 -no-debug -panic=trap`
+and copy the result to its Java resource name. The test helper and examples are
+TinyGo fixtures; they do not certify arbitrary standard Go runtimes.
+
 # WASM discovery callbacks
 
 `AbstractWasmDiscoveryHandler` forwards discovery updates to the required guest
@@ -33,3 +125,23 @@ The Go and Rust test guests read the deletion payload and 
echo it through
 and `src/test/rust-discovery-handler`. The 
`TestLegacyWasmPluginDiscoveryHandler`
 fixture preserves the original Go binary without the deletion export to test
 compatibility with existing guests.
+
+## CI fixture verification
+
+The `wasm-fixtures` workflow pins WABT 1.0.42 and Rust 1.90.0. It runs
+`python3 .github/scripts/verify-wasm-fixtures.py` when either WASM module 
changes.
+The script checks the gateway WAT binaries byte-for-byte without modifying the
+committed files, then compiles the five Rust guests for `wasm32-wasip1` and 
runs
+the WASM Java suites against them. Original resource bytes are restored even if
+a build or test fails. Legacy Rust compiler versions were not recorded, so
+Rust reproduction is checked by behavior rather than exact binary identity.
+The existing TinyGo guests run in the same suites. Use `--wat-only` or
+`--rust-only` for a scoped local run. Maven-only builds still use committed 
guests.
+
+The optional `removeDiscoveryUpstreamData` callback documented below is now on
+the merged base through #7568. The binary request profile in these gateway 
tests
+belongs to the explicit Java reference adapter; it is not the JSON default
+bridge introduced separately by #7569. Both use the same host import signatures
+and the merged policy that rejects non-function exports. This documentation PR
+keeps `_initialize` validation in the same private lookup helper so a malformed
+initializer is not silently treated as an absent optional hook.
diff --git a/shenyu-plugin/shenyu-plugin-wasm-base/pom.xml 
b/shenyu-plugin/shenyu-plugin-wasm-base/pom.xml
index b3e20c0b01..a19dd8a441 100644
--- a/shenyu-plugin/shenyu-plugin-wasm-base/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-wasm-base/pom.xml
@@ -36,6 +36,22 @@
             <artifactId>shenyu-plugin-base</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.shenyu</groupId>
+            <artifactId>shenyu-spring-boot-starter-gateway</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>ch.qos.logback</groupId>
             <artifactId>logback-classic</artifactId>
@@ -47,4 +63,18 @@
             <scope>test</scope>
         </dependency>
     </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes combine.children="append">
+                        <!-- Compiled fixtures; the corresponding WAT sources 
carry license headers. -->
+                        
<exclude>src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.*.wasm</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/ReferenceWasmPlugin.java
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/ReferenceWasmPlugin.java
new file mode 100644
index 0000000000..e200d87472
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/ReferenceWasmPlugin.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.wasm.base.gateway;
+
+import com.dylibso.chicory.runtime.HostFunction;
+import com.dylibso.chicory.runtime.Instance;
+import com.dylibso.chicory.runtime.Store;
+import com.dylibso.chicory.wasm.types.FunctionType;
+import com.dylibso.chicory.wasm.types.ValType;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.utils.WebFluxResultUtils;
+import org.apache.shenyu.plugin.wasm.api.exception.ShenyuWasmException;
+import org.apache.shenyu.plugin.wasm.base.AbstractShenyuWasmPlugin;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Reference token gate using the binary request profile documented in 
ABI-v1.md.
+ */
+public class ReferenceWasmPlugin extends AbstractShenyuWasmPlugin {
+
+    static final String NAME = "wasmReference";
+
+    static final String RESULT = ReferenceWasmPlugin.class.getName() + 
".result";
+
+    private static final String BODY = ReferenceWasmPlugin.class.getName() + 
".body";
+
+    private static final List<String> FIELDS = List.of("selectorId", 
"selectorHandle", "ruleId", "ruleHandle",
+            "method", "path", "query", "token", "requestId", "body");
+
+    private static final AtomicLong IDS = new AtomicLong();
+
+    public ReferenceWasmPlugin() {
+        super(ReferenceWasmPlugin::registerImports);
+    }
+
+    private static void registerImports(final Store store) {
+        FunctionType type = FunctionType.of(List.of(ValType.I64, ValType.I64, 
ValType.I32), List.of(ValType.I32));
+        store.addFunction(new HostFunction("shenyu", "get_args", type, 
ReferenceWasmPlugin::getArgs));
+        store.addFunction(new HostFunction("shenyu", "put_result", type, 
ReferenceWasmPlugin::putResult));
+    }
+
+    private static long[] getArgs(final Instance instance, final long... args) 
{
+        Argument argument = ARGUMENTS.get(args[0]);
+        if (Objects.isNull(argument)) {
+            return new long[]{-2};
+        }
+        ServerWebExchange exchange = argument.getExchange();
+        List<String> fields = List.of(argument.getSelector().getId(), 
argument.getSelector().getHandle(),
+                argument.getRule().getId(), argument.getRule().getHandle(), 
exchange.getRequest().getMethod().name(),
+                exchange.getRequest().getURI().getRawPath(), 
Objects.toString(exchange.getRequest().getURI().getRawQuery(), ""),
+                
Objects.toString(exchange.getRequest().getHeaders().getFirst("X-Token"), ""),
+                
Objects.toString(exchange.getRequest().getHeaders().getFirst("X-Request-Id"), 
""), exchange.getAttributeOrDefault(BODY, ""));
+        List<byte[]> bytes = fields.stream().map(field -> 
field.getBytes(StandardCharsets.UTF_8)).toList();
+        int size = Integer.BYTES + bytes.stream().mapToInt(field -> 
Integer.BYTES + field.length).sum();
+        if (args[2] < size) {
+            // No partial write: the guest retries with its full buffer.
+            return new long[]{-1};
+        }
+        ByteBuffer frame = 
ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
+        frame.putInt(1);
+        bytes.forEach(field -> frame.putInt(field.length).put(field));
+        instance.memory().write(Math.toIntExact(args[1]), frame.array());
+        return new long[]{size};
+    }
+
+    private static long[] putResult(final Instance instance, final long... 
args) {
+        Argument argument = ARGUMENTS.get(args[0]);
+        if (Objects.isNull(argument)) {
+            return new long[]{-2};
+        }
+        byte[] bytes = instance.memory().readBytes(Math.toIntExact(args[1]), 
Math.toIntExact(args[2]));
+        ByteBuffer frame = 
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
+        int decision = frame.getInt();
+        if (frame.getInt() != 1 || decision < 0 || decision > 1) {
+            throw new ShenyuWasmException("Invalid reference result version or 
decision");
+        }
+        Map<String, String> result = new LinkedHashMap<>();
+        result.put("decision", decision == 0 ? "allow" : "deny");
+        for (String field : FIELDS) {
+            int length = frame.getInt();
+            if (length < 0 || length > frame.remaining()) {
+                throw new ShenyuWasmException("Invalid reference result field 
length");
+            }
+            byte[] value = new byte[length];
+            frame.get(value);
+            result.put(field, new String(value, StandardCharsets.UTF_8));
+        }
+        if (frame.hasRemaining()) {
+            throw new ShenyuWasmException("Unexpected reference result bytes");
+        }
+        argument.getExchange().getAttributes().put(RESULT, result);
+        return new long[]{0};
+    }
+
+    @Override
+    public Mono<Void> execute(final ServerWebExchange exchange, final 
ShenyuPluginChain chain) {
+        // Buffer only in this example. A production adapter must define body 
size and streaming policy.
+        return DataBufferUtils.join(exchange.getRequest().getBody(), 
32768).map(buffer -> {
+            try {
+                byte[] body = new byte[buffer.readableByteCount()];
+                buffer.read(body);
+                return new String(body, StandardCharsets.UTF_8);
+            } finally {
+                DataBufferUtils.release(buffer);
+            }
+        }).defaultIfEmpty("").flatMap(body -> {
+            exchange.getAttributes().put(BODY, body);
+            // The loader owns one mutable instance. Hold the monitor for the 
synchronous
+            // guest invocation and result decoding, then run the reactive 
chain outside it.
+            synchronized (this) {
+                return super.execute(exchange, chain);
+            }
+        });
+    }
+
+    @Override
+    protected Mono<Void> doExecute(final ServerWebExchange exchange, final 
ShenyuPluginChain chain,
+                                   final SelectorData selector, final RuleData 
rule, final Long argumentId) {
+        Map<String, String> result = exchange.getAttribute(RESULT);
+        if (Objects.isNull(result)) {
+            return Mono.error(new ShenyuWasmException("Guest did not return a 
decision"));
+        }
+        exchange.getResponse().getHeaders().set("X-Wasm-Decision", 
result.get("decision"));
+        if ("deny".equals(result.get("decision"))) {
+            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
+            return WebFluxResultUtils.result(exchange, result);
+        }
+        return chain.execute(exchange);
+    }
+
+    @Override
+    protected Long getArgumentId(final ServerWebExchange exchange, final 
ShenyuPluginChain chain,
+                                 final SelectorData selector, final RuleData 
rule) {
+        return IDS.incrementAndGet();
+    }
+
+    @Override
+    public int getOrder() {
+        return -100;
+    }
+
+    @Override
+    public String named() {
+        return NAME;
+    }
+
+    static int pendingArguments() {
+        return ARGUMENTS.size();
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/ReferenceWasmPluginDataHandler.java
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/ReferenceWasmPluginDataHandler.java
new file mode 100644
index 0000000000..57a36c2197
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/ReferenceWasmPluginDataHandler.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.wasm.base.gateway;
+
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.utils.GsonUtils;
+import 
org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmPluginDataHandler;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Configuration hooks acknowledge the exact JSON received by the guest.
+ */
+public final class ReferenceWasmPluginDataHandler extends 
AbstractWasmPluginDataHandler {
+
+    private static final AtomicLong IDS = new AtomicLong();
+
+    private final List<Map<String, Object>> events = new 
CopyOnWriteArrayList<>();
+
+    @Override
+    protected long onGetArgs(final long argId, final long addr, final int len) 
{
+        Object data = PLUGIN_ARGUMENTS.get(argId);
+        if (Objects.isNull(data)) {
+            data = SELECTOR_ARGUMENTS.get(argId);
+        }
+        if (Objects.isNull(data)) {
+            data = RULE_ARGUMENTS.get(argId);
+        }
+        if (Objects.isNull(data)) {
+            return -2;
+        }
+        byte[] bytes = 
GsonUtils.getInstance().toJson(data).getBytes(StandardCharsets.UTF_8);
+        if (bytes.length > len) {
+            return -1;
+        }
+        getMemory().write(Math.toIntExact(addr), bytes);
+        return bytes.length;
+    }
+
+    @Override
+    protected long onPutResult(final long argId, final long addr, final int 
len) {
+        byte[] bytes = getMemory().readBytes(Math.toIntExact(addr), len);
+        int event = 
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
+        String json = new String(bytes, Integer.BYTES, bytes.length - 
Integer.BYTES, StandardCharsets.UTF_8);
+        events.add(Map.of("event", event, "json", json));
+        return 0;
+    }
+
+    @Override
+    protected Long getPluginArgumentId(final PluginData data) {
+        return IDS.incrementAndGet();
+    }
+
+    @Override
+    protected Long getSelectorArgumentId(final SelectorData data) {
+        return IDS.incrementAndGet();
+    }
+
+    @Override
+    protected Long getRuleArgumentId(final RuleData data) {
+        return IDS.incrementAndGet();
+    }
+
+    @Override
+    public synchronized void handlerPlugin(final PluginData data) {
+        super.handlerPlugin(data);
+    }
+
+    @Override
+    public synchronized void removePlugin(final PluginData data) {
+        super.removePlugin(data);
+    }
+
+    @Override
+    public synchronized void handlerSelector(final SelectorData data) {
+        super.handlerSelector(data);
+    }
+
+    @Override
+    public synchronized void removeSelector(final SelectorData data) {
+        super.removeSelector(data);
+    }
+
+    @Override
+    public synchronized void handlerRule(final RuleData data) {
+        super.handlerRule(data);
+    }
+
+    @Override
+    public synchronized void removeRule(final RuleData data) {
+        super.removeRule(data);
+    }
+
+    @Override
+    public String pluginNamed() {
+        return ReferenceWasmPlugin.NAME;
+    }
+
+    List<Map<String, Object>> events() {
+        return events;
+    }
+
+    static int pendingArguments() {
+        return PLUGIN_ARGUMENTS.size() + SELECTOR_ARGUMENTS.size() + 
RULE_ARGUMENTS.size();
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/WasmGatewayTest.java
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/WasmGatewayTest.java
new file mode 100644
index 0000000000..f04fe1a03e
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/WasmGatewayTest.java
@@ -0,0 +1,364 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.wasm.base.gateway;
+
+import org.apache.shenyu.common.dto.ConditionData;
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.SelectorTypeEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.plugin.api.ShenyuPlugin;
+import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
+import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
+import org.apache.shenyu.plugin.api.utils.WebFluxResultUtils;
+import org.apache.shenyu.plugin.base.cache.BaseDataCache;
+import org.apache.shenyu.plugin.base.cache.MatchDataCache;
+import 
org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmPluginDataHandler;
+import org.apache.shenyu.sync.data.api.PluginDataSubscriber;
+import org.apache.shenyu.web.handler.ShenyuWebHandler;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Bootstraps the gateway starter on a real HTTP port with a registered WASM 
plugin.
+ */
+@SpringBootTest(classes = WasmGatewayTest.GatewayApplication.class, 
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
+        properties = {"spring.main.web-application-type=reactive", 
"spring.main.allow-bean-definition-overriding=true",
+                "shenyu.local.enabled=false", "shenyu.alert.enabled=false"})
+public final class WasmGatewayTest {
+
+    @Autowired
+    private ConfigurableApplicationContext context;
+
+    @Autowired
+    private PluginDataSubscriber subscriber;
+
+    @Autowired
+    private ShenyuWebHandler webHandler;
+
+    @Autowired
+    private ReferenceWasmPlugin plugin;
+
+    @Autowired
+    private ReferenceWasmPluginDataHandler handler;
+
+    @LocalServerPort
+    private int port;
+
+    private WebTestClient client;
+
+    private PluginData pluginData;
+
+    private SelectorData selector;
+
+    private RuleData rule;
+
+    @BeforeEach
+    public void setUp() {
+        // Other module tests install mock contexts in this static utility.
+        SpringBeanUtils.getInstance().setApplicationContext(context);
+        clearCaches();
+        handler.events().clear();
+        webHandler.putExtPlugins(List.of(plugin));
+        client = WebTestClient.bindToServer().baseUrl("http://localhost:"; + 
port).responseTimeout(Duration.ofSeconds(15)).build();
+        pluginData = 
PluginData.builder().id("wasm-plugin").name(ReferenceWasmPlugin.NAME).enabled(true).sort(-100).config("v1").build();
+        selector = 
SelectorData.builder().id("wasm-selector").pluginName(ReferenceWasmPlugin.NAME).enabled(true).sort(0)
+                
.type(SelectorTypeEnum.CUSTOM_FLOW.getCode()).continued(true).matchMode(0).matchRestful(false).logged(false)
+                
.handle("/wasm/").conditionList(List.of(uriCondition())).build();
+        rule = 
RuleData.builder().id("wasm-rule").selectorId(selector.getId()).pluginName(ReferenceWasmPlugin.NAME)
+                
.enabled(true).sort(0).matchMode(0).matchRestful(false).loged(false).handle("secret")
+                .conditionDataList(List.of(uriCondition())).build();
+        subscriber.onSubscribe(pluginData);
+        subscriber.onSelectorSubscribe(selector);
+        subscriber.onRuleSubscribe(rule);
+    }
+
+    @AfterEach
+    public void tearDown() {
+        assertEquals(0, ReferenceWasmPlugin.pendingArguments());
+        assertEquals(0, ReferenceWasmPluginDataHandler.pendingArguments());
+        clearCaches();
+        webHandler.putExtPlugins(List.of(plugin));
+    }
+
+    @Test
+    public void requestDataAndGuestDecisionControlTheChain() {
+        assertTrue(webHandler.getPlugins().contains(plugin));
+        request("secret", "request-1", "hello 世界").expectStatus().isOk()
+                .expectHeader().valueEquals("X-Wasm-Decision", "allow")
+                .expectHeader().valueEquals("X-Chain-Reached", "true")
+                
.expectBody().jsonPath("$.selectorId").isEqualTo(selector.getId())
+                .jsonPath("$.selectorHandle").isEqualTo("/wasm/")
+                
.jsonPath("$.ruleId").isEqualTo(rule.getId()).jsonPath("$.ruleHandle").isEqualTo("secret")
+                
.jsonPath("$.method").isEqualTo("POST").jsonPath("$.path").isEqualTo("/wasm/check")
+                
.jsonPath("$.query").isEqualTo("source=gateway").jsonPath("$.token").isEqualTo("secret")
+                
.jsonPath("$.requestId").isEqualTo("request-1").jsonPath("$.body").isEqualTo("hello
 世界");
+        request("wrong", "request-2", "rejected").expectStatus().isForbidden()
+                .expectHeader().valueEquals("X-Wasm-Decision", 
"deny").expectHeader().doesNotExist("X-Chain-Reached")
+                
.expectBody().jsonPath("$.decision").isEqualTo("deny").jsonPath("$.requestId").isEqualTo("request-2");
+    }
+
+    @Test
+    public void 
configurationUpdatesAndDeletesReachTheGuestAndInvalidateMatching() {
+        assertEvent(0, 0, pluginData);
+        assertEvent(1, 1, selector);
+        assertEvent(2, 2, rule);
+        request("secret", "initial", "").expectStatus().isOk();
+        rule.setHandle("updated");
+        subscriber.onRuleSubscribe(rule);
+        assertEvent(3, 2, rule);
+        request("secret", "old-token", "").expectStatus().isForbidden();
+        request("updated", "new-token", "").expectStatus().isOk();
+        selector.setHandle("/different/");
+        subscriber.onSelectorSubscribe(selector);
+        assertEvent(4, 1, selector);
+        request("updated", "new-selector", "").expectStatus().isForbidden();
+        selector.setHandle("/wasm/");
+        subscriber.onSelectorSubscribe(selector);
+        subscriber.unRuleSubscribe(rule);
+        assertEvent(6, 5, rule);
+        expectBypass();
+        subscriber.onRuleSubscribe(rule);
+        request("updated", "rule-restored", "").expectStatus().isOk();
+        subscriber.unSelectorSubscribe(selector);
+        assertEvent(8, 4, selector);
+        expectBypass();
+        subscriber.onSelectorSubscribe(selector);
+        subscriber.onRuleSubscribe(rule);
+        request("updated", "selector-restored", "").expectStatus().isOk();
+        pluginData.setConfig("v2");
+        pluginData.setEnabled(false);
+        subscriber.onSubscribe(pluginData);
+        assertEvent(11, 0, pluginData);
+        expectBypass();
+        pluginData.setEnabled(true);
+        subscriber.onSubscribe(pluginData);
+        request("updated", "plugin-restored", "").expectStatus().isOk();
+        subscriber.unSubscribe(pluginData);
+        assertEvent(13, 3, pluginData);
+        expectBypass();
+    }
+
+    @Test
+    public void unmatchedRequestsContinueWithoutInvokingTheGuest() {
+        client.get().uri("/outside").exchange().expectStatus().isOk()
+                
.expectHeader().doesNotExist("X-Wasm-Decision").expectHeader().valueEquals("X-Chain-Reached",
 "true");
+    }
+
+    @Test
+    public void concurrentRequestsKeepArgumentsMemoryAndResultsIsolated() 
throws Exception {
+        ExecutorService executor = Executors.newFixedThreadPool(12);
+        try {
+            List<Callable<Void>> calls = new ArrayList<>();
+            for (int i = 0; i < 60; i++) {
+                final String id = "parallel-" + i;
+                final boolean allow = i % 2 == 0;
+                calls.add(() -> {
+                    WebTestClient.ResponseSpec response = request(allow ? 
"secret" : id, id, "body-" + id);
+                    response.expectStatus().isEqualTo(allow ? HttpStatus.OK : 
HttpStatus.FORBIDDEN);
+                    if (allow) {
+                        response.expectHeader().valueEquals("X-Chain-Reached", 
"true");
+                    } else {
+                        
response.expectHeader().doesNotExist("X-Chain-Reached");
+                    }
+                    
response.expectBody().jsonPath("$.decision").isEqualTo(allow ? "allow" : "deny")
+                            
.jsonPath("$.requestId").isEqualTo(id).jsonPath("$.body").isEqualTo("body-" + 
id);
+                    return null;
+                });
+            }
+            for (Future<Void> future : executor.invokeAll(calls, 30, 
TimeUnit.SECONDS)) {
+                future.get();
+            }
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+
+    @Test
+    public void missingRequiredExecutionReturnsTheGatewayError() {
+        webHandler.putExtPlugins(List.of(new MissingExecutePlugin()));
+        request("secret", "missing-export", 
"").expectStatus().is5xxServerError()
+                .expectHeader().doesNotExist("X-Chain-Reached")
+                
.expectBody().jsonPath("$.code").isEqualTo(ShenyuResultEnum.WASM_FUNC_NOT_FOUND.getCode());
+    }
+
+    @Test
+    public void missingConfigurationHooksAreOptional() {
+        try (MissingHooksHandler missing = new MissingHooksHandler()) {
+            missing.handlerPlugin(pluginData);
+            missing.handlerSelector(selector);
+            missing.handlerRule(rule);
+            missing.removeRule(rule);
+            missing.removeSelector(selector);
+            missing.removePlugin(pluginData);
+            assertFalse(missing.getWasmExtern("handlerRule").isPresent());
+        }
+    }
+
+    @Test
+    public void guestTrapFailsTheRequestAndReleasesArguments() {
+        webHandler.putExtPlugins(List.of(new TrappingPlugin()));
+        expectFailureAndRecovery();
+    }
+
+    @Test
+    public void invalidGuestMemoryFailsTheRequestAndReleasesArguments() {
+        webHandler.putExtPlugins(List.of(new InvalidMemoryPlugin()));
+        expectFailureAndRecovery();
+    }
+
+    @Test
+    public void unsupportedGuestResultVersionFailsTheRequest() {
+        webHandler.putExtPlugins(List.of(new IncompatibleResultPlugin()));
+        expectFailureAndRecovery();
+    }
+
+    private void expectFailureAndRecovery() {
+        request("secret", "failure", 
"").expectStatus().is5xxServerError().expectHeader().doesNotExist("X-Chain-Reached");
+        assertEquals(0, ReferenceWasmPlugin.pendingArguments());
+        webHandler.putExtPlugins(List.of(plugin));
+        request("secret", "recovery", "after failure").expectStatus().isOk()
+                .expectBody().jsonPath("$.requestId").isEqualTo("recovery");
+    }
+
+    private WebTestClient.ResponseSpec request(final String token, final 
String id, final String body) {
+        return 
client.post().uri("/wasm/check?source=gateway").header("X-Token", 
token).header("X-Request-Id", id)
+                .contentType(MediaType.TEXT_PLAIN).bodyValue(body).exchange();
+    }
+
+    private void expectBypass() {
+        request("updated", "bypass", 
"").expectStatus().isOk().expectHeader().doesNotExist("X-Wasm-Decision")
+                .expectHeader().valueEquals("X-Chain-Reached", "true");
+    }
+
+    private void assertEvent(final int index, final int event, final Object 
data) {
+        assertEquals(event, handler.events().get(index).get("event"));
+        assertEquals(GsonUtils.getInstance().toJson(data), 
handler.events().get(index).get("json"));
+    }
+
+    private static ConditionData uriCondition() {
+        ConditionData condition = new ConditionData();
+        condition.setParamType("uri");
+        condition.setParamName("/");
+        condition.setOperator("match");
+        condition.setParamValue("/wasm/**");
+        return condition;
+    }
+
+    private static void clearCaches() {
+        BaseDataCache.getInstance().cleanPluginData();
+        BaseDataCache.getInstance().cleanSelectorData();
+        BaseDataCache.getInstance().cleanRuleData();
+        MatchDataCache.getInstance().cleanSelectorData();
+        MatchDataCache.getInstance().cleanRuleDataData();
+    }
+
+    /**
+     * Minimal gateway bootstrap: the same gateway starter used by 
shenyu-bootstrap.
+     */
+    @SpringBootConfiguration
+    @EnableAutoConfiguration
+    static class GatewayApplication {
+
+        @Bean
+        ReferenceWasmPlugin referenceWasmPlugin() {
+            return new ReferenceWasmPlugin();
+        }
+
+        @Bean
+        ReferenceWasmPluginDataHandler referenceWasmPluginDataHandler() {
+            return new ReferenceWasmPluginDataHandler();
+        }
+
+        @Bean
+        ShenyuPlugin terminalPlugin() {
+            return new ShenyuPlugin() {
+                @Override
+                public Mono<Void> execute(final ServerWebExchange exchange, 
final ShenyuPluginChain chain) {
+                    exchange.getResponse().getHeaders().set("X-Chain-Reached", 
"true");
+                    Object result = 
exchange.getAttribute(ReferenceWasmPlugin.RESULT);
+                    return WebFluxResultUtils.result(exchange, 
Objects.nonNull(result) ? result : Map.of("bypass", true));
+                }
+
+                @Override
+                public int getOrder() {
+                    return -90;
+                }
+
+                @Override
+                public String named() {
+                    return "wasmTerminal";
+                }
+            };
+        }
+    }
+
+    /** Loads the memory-only fixture without a doExecute export. */
+    static final class MissingExecutePlugin extends ReferenceWasmPlugin {
+    }
+
+    /** Loads a guest that traps after reading its arguments. */
+    static final class TrappingPlugin extends ReferenceWasmPlugin {
+    }
+
+    /** Loads a guest that passes an out-of-bounds result pointer. */
+    static final class InvalidMemoryPlugin extends ReferenceWasmPlugin {
+    }
+
+    /** Loads a guest returning an unsupported reference profile version. */
+    static final class IncompatibleResultPlugin extends ReferenceWasmPlugin {
+    }
+
+    /** Loads a handler with none of the optional configuration exports. */
+    static final class MissingHooksHandler extends 
AbstractWasmPluginDataHandler {
+        @Override
+        public String pluginNamed() {
+            return ReferenceWasmPlugin.NAME;
+        }
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/WasmLifecycleContractTest.java
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/WasmLifecycleContractTest.java
new file mode 100644
index 0000000000..73fea71fa2
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/java/org/apache/shenyu/plugin/wasm/base/gateway/WasmLifecycleContractTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.wasm.base.gateway;
+
+import com.dylibso.chicory.wasm.InvalidException;
+import org.apache.shenyu.common.dto.DiscoverySyncData;
+import org.apache.shenyu.common.dto.MetaData;
+import org.apache.shenyu.plugin.wasm.api.exception.ShenyuWasmInitException;
+import org.apache.shenyu.plugin.wasm.api.loader.WasmLoader;
+import org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmDiscoveryHandler;
+import org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmMetaDataHandler;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Verifies optional and required lifecycle exports against actual Wasm 
modules.
+ */
+public final class WasmLifecycleContractTest {
+
+    @Test
+    public void absentMetadataOperationsAreRequiredButRefreshIsOptional() {
+        try (MissingMetadataHandler handler = new MissingMetadataHandler()) {
+            assertThrows(ShenyuWasmInitException.class, () -> 
handler.handle(new MetaData()));
+            assertThrows(ShenyuWasmInitException.class, () -> 
handler.remove(new MetaData()));
+            handler.refresh();
+            assertFalse(handler.getWasmExtern("_initialize").isPresent());
+        }
+    }
+
+    @Test
+    public void absentDiscoveryOperationIsRequired() {
+        try (MissingDiscoveryHandler handler = new MissingDiscoveryHandler()) {
+            assertThrows(ShenyuWasmInitException.class, () -> 
handler.handlerDiscoveryUpstreamData(new DiscoverySyncData()));
+        }
+    }
+
+    @Test
+    public void exportOfTheWrongKindIsNotAnAbsentOptionalFunction() {
+        try (InvalidHookLoader loader = new InvalidHookLoader()) {
+            assertThrows(InvalidException.class, () -> 
loader.getWasmExtern("before"));
+        }
+    }
+
+    @Test
+    public void invalidInitializerIsNotIgnored() {
+        assertThrows(InvalidException.class, InvalidInitializerLoader::new);
+    }
+
+    /** Memory-only metadata fixture. */
+    static final class MissingMetadataHandler extends 
AbstractWasmMetaDataHandler {
+        @Override
+        public String rpcType() {
+            return "http";
+        }
+
+        @Override
+        protected Long getArgumentId(final MetaData data) {
+            return 1L;
+        }
+    }
+
+    /** Memory-only discovery fixture. */
+    static final class MissingDiscoveryHandler extends 
AbstractWasmDiscoveryHandler {
+        @Override
+        public String pluginName() {
+            return ReferenceWasmPlugin.NAME;
+        }
+
+        @Override
+        protected Long getArgumentId(final DiscoverySyncData data) {
+            return 1L;
+        }
+    }
+
+    /** Fixture exporting a global named before. */
+    static final class InvalidHookLoader extends WasmLoader {
+    }
+
+    /** Fixture exporting a global named _initialize. */
+    static final class InvalidInitializerLoader extends WasmLoader {
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.ReferenceWasmPlugin.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.ReferenceWasmPlugin.wasm
new file mode 100644
index 0000000000..104ff1d6f7
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.ReferenceWasmPlugin.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.ReferenceWasmPluginDataHandler.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.ReferenceWasmPluginDataHandler.wasm
new file mode 100644
index 0000000000..4bd0c334b5
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.ReferenceWasmPluginDataHandler.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$IncompatibleResultPlugin.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$IncompatibleResultPlugin.wasm
new file mode 100644
index 0000000000..636f5b4ce7
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$IncompatibleResultPlugin.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$InvalidMemoryPlugin.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$InvalidMemoryPlugin.wasm
new file mode 100644
index 0000000000..8f7d4f8d1b
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$InvalidMemoryPlugin.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$MissingExecutePlugin.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$MissingExecutePlugin.wasm
new file mode 100644
index 0000000000..3a12363fcf
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$MissingExecutePlugin.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$MissingHooksHandler.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$MissingHooksHandler.wasm
new file mode 100644
index 0000000000..3a12363fcf
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$MissingHooksHandler.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$TrappingPlugin.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$TrappingPlugin.wasm
new file mode 100644
index 0000000000..12dfaee0c4
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmGatewayTest$TrappingPlugin.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$InvalidHookLoader.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$InvalidHookLoader.wasm
new file mode 100644
index 0000000000..2de457dd86
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$InvalidHookLoader.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$InvalidInitializerLoader.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$InvalidInitializerLoader.wasm
new file mode 100644
index 0000000000..e3a3a7ebea
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$InvalidInitializerLoader.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$MissingDiscoveryHandler.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$MissingDiscoveryHandler.wasm
new file mode 100644
index 0000000000..3a12363fcf
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$MissingDiscoveryHandler.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$MissingMetadataHandler.wasm
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$MissingMetadataHandler.wasm
new file mode 100644
index 0000000000..3a12363fcf
Binary files /dev/null and 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/resources/org.apache.shenyu.plugin.wasm.base.gateway.WasmLifecycleContractTest$MissingMetadataHandler.wasm
 differ
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/Makefile 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/Makefile
new file mode 100644
index 0000000000..7519028a5a
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/Makefile
@@ -0,0 +1,36 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+WAT2WASM ?= wat2wasm
+RESOURCES := ../resources
+PREFIX := org.apache.shenyu.plugin.wasm.base.gateway.
+
+.PHONY: build
+build:
+       $(WAT2WASM) reference-plugin.wat -o 
$(RESOURCES)/$(PREFIX)ReferenceWasmPlugin.wasm
+       $(WAT2WASM) configuration-handler.wat -o 
$(RESOURCES)/$(PREFIX)ReferenceWasmPluginDataHandler.wasm
+       $(WAT2WASM) missing-hooks.wat -o 
'$(RESOURCES)/$(PREFIX)WasmGatewayTest$$MissingExecutePlugin.wasm'
+       $(WAT2WASM) missing-hooks.wat -o 
'$(RESOURCES)/$(PREFIX)WasmGatewayTest$$MissingHooksHandler.wasm'
+       $(WAT2WASM) trap.wat -o 
'$(RESOURCES)/$(PREFIX)WasmGatewayTest$$TrappingPlugin.wasm'
+       $(WAT2WASM) invalid-memory.wat -o 
'$(RESOURCES)/$(PREFIX)WasmGatewayTest$$InvalidMemoryPlugin.wasm'
+       $(WAT2WASM) missing-hooks.wat -o 
'$(RESOURCES)/$(PREFIX)WasmLifecycleContractTest$$MissingMetadataHandler.wasm'
+       $(WAT2WASM) missing-hooks.wat -o 
'$(RESOURCES)/$(PREFIX)WasmLifecycleContractTest$$MissingDiscoveryHandler.wasm'
+       $(WAT2WASM) invalid-hook.wat -o 
'$(RESOURCES)/$(PREFIX)WasmLifecycleContractTest$$InvalidHookLoader.wasm'
+       $(WAT2WASM) invalid-initializer.wat -o 
'$(RESOURCES)/$(PREFIX)WasmLifecycleContractTest$$InvalidInitializerLoader.wasm'
+       $(WAT2WASM) incompatible-result.wat -o 
'$(RESOURCES)/$(PREFIX)WasmGatewayTest$$IncompatibleResultPlugin.wasm'
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/configuration-handler.wat
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/configuration-handler.wat
new file mode 100644
index 0000000000..e154032d45
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/configuration-handler.wat
@@ -0,0 +1,34 @@
+(;
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+;)
+
+(module
+  (import "shenyu" "get_args" (func $get_args (param i64 i64 i32) (result 
i32)))
+  (import "shenyu" "put_result" (func $put_result (param i64 i64 i32) (result 
i32)))
+  (memory (export "memory") 1)
+  (func $ack (param $id i64) (param $event i32)
+    (local $length i32)
+    (local.set $length (call $get_args (local.get $id) (i64.const 16) 
(i32.const 65520)))
+    (if (i32.le_s (local.get $length) (i32.const 0)) (then unreachable))
+    (i32.store (i32.const 12) (local.get $event))
+    (if (call $put_result (local.get $id) (i64.const 12)
+          (i32.add (local.get $length) (i32.const 4))) (then unreachable)))
+  (func (export "handlerPlugin") (param $id i64) (call $ack (local.get $id) 
(i32.const 0)))
+  (func (export "handlerSelector") (param $id i64) (call $ack (local.get $id) 
(i32.const 1)))
+  (func (export "handlerRule") (param $id i64) (call $ack (local.get $id) 
(i32.const 2)))
+  (func (export "removePlugin") (param $id i64) (call $ack (local.get $id) 
(i32.const 3)))
+  (func (export "removeSelector") (param $id i64) (call $ack (local.get $id) 
(i32.const 4)))
+  (func (export "removeRule") (param $id i64) (call $ack (local.get $id) 
(i32.const 5))))
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/incompatible-result.wat
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/incompatible-result.wat
new file mode 100644
index 0000000000..8c981ebfa2
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/incompatible-result.wat
@@ -0,0 +1,24 @@
+(;
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+;)
+
+(module
+  (import "shenyu" "put_result" (func $put_result (param i64 i64 i32) (result 
i32)))
+  (memory (export "memory") 1)
+  (func (export "doExecute") (param $id i64)
+    (i32.store (i32.const 12) (i32.const 0))
+    (i32.store (i32.const 16) (i32.const 2))
+    (drop (call $put_result (local.get $id) (i64.const 12) (i32.const 8)))))
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/invalid-hook.wat 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/invalid-hook.wat
new file mode 100644
index 0000000000..1a1e48910e
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/invalid-hook.wat
@@ -0,0 +1,20 @@
+(;
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+;)
+
+(module
+  (memory (export "memory") 1)
+  (global (export "before") i32 (i32.const 0)))
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/invalid-initializer.wat
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/invalid-initializer.wat
new file mode 100644
index 0000000000..0a7dc2c61b
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/invalid-initializer.wat
@@ -0,0 +1,20 @@
+(;
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+;)
+
+(module
+  (memory (export "memory") 1)
+  (global (export "_initialize") i32 (i32.const 0)))
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/invalid-memory.wat
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/invalid-memory.wat
new file mode 100644
index 0000000000..cf215b1566
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/invalid-memory.wat
@@ -0,0 +1,22 @@
+(;
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+;)
+
+(module
+  (import "shenyu" "put_result" (func $put_result (param i64 i64 i32) (result 
i32)))
+  (memory (export "memory") 1)
+  (func (export "doExecute") (param $id i64)
+    (drop (call $put_result (local.get $id) (i64.const 65535) (i32.const 8)))))
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/missing-hooks.wat 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/missing-hooks.wat
new file mode 100644
index 0000000000..532ccc4fa9
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/missing-hooks.wat
@@ -0,0 +1,18 @@
+(;
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+;)
+
+(module (memory (export "memory") 1))
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/reference-plugin.wat
 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/reference-plugin.wat
new file mode 100644
index 0000000000..3ba4fa6700
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/reference-plugin.wat
@@ -0,0 +1,72 @@
+(;
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+;)
+
+(module
+  (import "shenyu" "get_args" (func $get_args (param i64 i64 i32) (result 
i32)))
+  (import "shenyu" "put_result" (func $put_result (param i64 i64 i32) (result 
i32)))
+  (memory (export "memory") 1)
+
+  ;; Return the address of the length prefix of field n in the request frame.
+  (func $field (param $n i32) (result i32)
+    (local $p i32)
+    (local.set $p (i32.const 20))
+    (block $done
+      (loop $next
+        (br_if $done (i32.eqz (local.get $n)))
+        (local.set $p (i32.add (local.get $p)
+          (i32.add (i32.const 4) (i32.load (local.get $p)))))
+        (local.set $n (i32.sub (local.get $n) (i32.const 1)))
+        (br $next)))
+    (local.get $p))
+
+  ;; Compare two length-prefixed UTF-8 fields (exact equality or prefix).
+  (func $matches (param $a i32) (param $b i32) (param $prefix i32) (result i32)
+    (local $length i32) (local $i i32)
+    (local.set $length (i32.load (local.get $a)))
+    (if (i32.gt_u (local.get $length) (i32.load (local.get $b)))
+      (then (return (i32.const 0))))
+    (if (i32.and (i32.eqz (local.get $prefix))
+          (i32.ne (local.get $length) (i32.load (local.get $b))))
+      (then (return (i32.const 0))))
+    (block $done
+      (loop $next
+        (br_if $done (i32.eq (local.get $i) (local.get $length)))
+        (if (i32.ne
+              (i32.load8_u (i32.add (i32.add (local.get $a) (i32.const 4)) 
(local.get $i)))
+              (i32.load8_u (i32.add (i32.add (local.get $b) (i32.const 4)) 
(local.get $i))))
+          (then (return (i32.const 0))))
+        (local.set $i (i32.add (local.get $i) (i32.const 1)))
+        (br $next)))
+    (i32.const 1))
+
+  (func (export "doExecute") (param $id i64)
+    (local $length i32) (local $allow i32)
+    ;; Deliberately exercise the reference adapter's no-partial-write contract.
+    (if (i32.ne (call $get_args (local.get $id) (i64.const 16) (i32.const 1)) 
(i32.const -1))
+      (then unreachable))
+    (local.set $length (call $get_args (local.get $id) (i64.const 16) 
(i32.const 65520)))
+    (if (i32.le_s (local.get $length) (i32.const 0)) (then unreachable))
+    (if (i32.ne (i32.load (i32.const 16)) (i32.const 1)) (then unreachable))
+    ;; Guest policy: request token equals rule handle AND selector handle 
prefixes path.
+    (local.set $allow (i32.and
+      (call $matches (call $field (i32.const 3)) (call $field (i32.const 7)) 
(i32.const 0))
+      (call $matches (call $field (i32.const 1)) (call $field (i32.const 5)) 
(i32.const 1))))
+    ;; Return the decision followed by the original frame, proving what the 
guest read.
+    (i32.store (i32.const 12) (i32.eqz (local.get $allow)))
+    (if (call $put_result (local.get $id) (i64.const 12)
+          (i32.add (local.get $length) (i32.const 4))) (then unreachable)))
+)
diff --git 
a/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/trap.wat 
b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/trap.wat
new file mode 100644
index 0000000000..287e09416d
--- /dev/null
+++ b/shenyu-plugin/shenyu-plugin-wasm-base/src/test/wasm-gateway/trap.wat
@@ -0,0 +1,23 @@
+(;
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+;)
+
+(module
+  (import "shenyu" "get_args" (func $get_args (param i64 i64 i32) (result 
i32)))
+  (memory (export "memory") 1)
+  (func (export "doExecute") (param $id i64)
+    (drop (call $get_args (local.get $id) (i64.const 16) (i32.const 65520)))
+    unreachable))

Reply via email to