29 KiB
Plugin Chassis (PR #5651) — Reviewer's Guide
This guide exists because PR #5651 is unusually large (105 files, +48,749 / −32 lines, ~150 commits over three weeks) and was built incrementally by merging several feature branches. Reading it commit-by-commit is not productive. Reading it file-by-file without context is also not productive. This doc gives you a structured walk-through so you can validate each layer independently.
Companion documents:
ABI.md— the plugin ABI contract: what's stable, what's tail-extensible, how versioning works.FILE_CHANGES.md— file-by-file inventory (~140 files described, grouped by area).
For plugin authors who just want to write a plugin, see doc/PLUGIN_API.md. This document is for someone reviewing the plumbing.
1. What this PR does (in one paragraph)
It introduces a plugin chassis — a generic ABI plus a dlopen-based loader inside libproxysql.a — and uses that chassis to ship the first plugin: a MySQL X Protocol terminator that builds as a separate .so. The chassis lives behind #ifdef PROXYSQL40 (a new feature tier; PROXYSQLGENAI now implies it). Under any v3.x build (make with no flags, make PROXYSQL31=1) the chassis is fully invisible — no symbols, no headers, no behaviour change. The mysqlx plugin is the prototype consumer of the chassis; it auths clients, routes queries to a backend MySQL via X Protocol, optionally terminates client TLS / requires backend TLS, pools backend connections, and negotiates zstd / lz4 X Protocol compression.
┌─────────────────────────────────────────────┐
│ proxysql binary │
│ ┌──────────────────────────────────────┐ │
│ │ libproxysql.a │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ ProxySQL_PluginManager │ │ │
│ │ │ (the chassis) │ │ │
│ │ │ - dlopen + RTLD_LOCAL │ │ │
│ │ │ - 4-phase lifecycle │ │ │
│ │ │ - services injection │ │ │
│ │ │ - admin-command dispatch │ │ │
│ │ │ - query-hook dispatch │ │ │
│ │ └────────────────────────────────┘ │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
│ dlopen
▼
┌─────────────────────────────────────────────┐
│ ProxySQL_MySQLX_Plugin.so │
│ - X Protocol auth + routing + compression │
│ - ~5,600 LOC handwritten │
│ - protobuf 3.x (statically vendored zstd, │
│ lz4) │
└─────────────────────────────────────────────┘
The chassis is designed so the same pattern can host other plugins later (PgBouncer-protocol bridge, custom auth plugins, telemetry plugins, etc.) without reopening this PR.
2. Reading orders
Pick one based on your time budget. Each is cumulative — the 2-hour pass continues from where the 30-minute one ends.
30-minute pass: "is this load-bearing?"
Goal: convince yourself the chassis ABI and lifecycle are sane and the v3.x invisibility is real. Don't read mysqlx; trust that it's a consumer.
- Read §3 (ABI surface) below. Cross-check against
ABI.md. - Skim
include/ProxySQL_Plugin.h(324 LOC) — notePROXYSQL_PLUGIN_ABI_VERSION = 2, the descriptor struct, the services struct. - Read §4 (Four-phase lifecycle) below.
- Skim
lib/ProxySQL_PluginManager.cpplines 324–461 (load + register_schemas + abi_version gating) and 548–576 (stop_all pairs with init). - Check the v3.x invisibility claim:
grep -L PROXYSQL40 include/ProxySQL_Plugin.h include/ProxySQL_PluginManager.h lib/ProxySQL_PluginManager.cppshould be empty (every chassis file is wrapped).- The chassis-related deltas in
lib/Admin_Bootstrap.cpp(line ~944) andlib/ProxySQL_Admin.cpp(line ~342) are inside#ifdef PROXYSQL40blocks.
- Skim §7 (Seams where consistency could break) below — this is the list of things the incremental development pattern could have left contradictions in. Each item has a one-line check.
2-hour pass: "is this really designed?"
Goal: walk one full request path through the chassis and through mysqlx. Convince yourself the design is coherent end-to-end.
- Read §5 (Mysqlx as worked example) below.
- Trace one client connection through the code:
Mysqlx_Thread::accept_new_connection()(plugins/mysqlx/src/mysqlx_thread.cpp:200)MysqlxSession::handler()switch (plugins/mysqlx/src/mysqlx_session.cpp:187)handler_capabilities_get()→handler_capabilities_set()(compression + tls negotiation)handler_auth_start()→handle_auth_mysql41()→resolve_backend_target()→send_auth_ok()handler_connecting_server()(mysqlx_session.cpp:1028) — the backend auth handshakehandler_waiting_client_msg()/handler_waiting_server_msg()— the steady-state pump
- Read §6 (Build & CI) below.
- Verify a v3.0 build still works locally:
make clean && make(with no flags). Should producesrc/proxysqlwith no chassis symbols (nm src/proxysql | grep -i plugin_managershould be empty).
Full day: "I'm going to vouch for this on v3.0."
Goal: validate the entire test corpus, run the unit tests, and exercise the e2e against a real MySQL.
- Read
FILE_CHANGES.mdend-to-end. - Run the test suite per the verification checklist at the end of this doc.
- Build with
PROXYSQLGENAI=1and run a local mysqlx connection through ProxySQL against a Docker MySQL 8.4. The CI workflow.github/workflows/CI-mysqlx.ymldocuments the commands.
3. The chassis ABI surface
The chassis is defined by two headers plus the loader implementation. The contract a plugin author sees is everything declared in include/ProxySQL_Plugin.h. The contract the proxysql core sees is everything declared in include/ProxySQL_PluginManager.h.
Key types (in include/ProxySQL_Plugin.h):
PROXYSQL_PLUGIN_ABI_VERSION(currently2) — what newly-built plugins target. ABI 1 was the original 6-field descriptor; ABI 2 appendsregister_schemasfor the four-phase lifecycle.ProxySQL_PluginDescriptor— 7-field struct returned viaextern "C" proxysql_plugin_descriptor_v1(). The single mandatory entry point a plugin must export.ProxySQL_PluginServices— services struct injected into the plugin: table/command/query-hook registration, log helper, three DB getters, prometheus registry. Tail-append discipline preserves ABI compatibility.
The contract of the descriptor is:
nameis a non-null, non-empty C string. The loader rejects anything else.abi_version ∈ [1, PROXYSQL_PLUGIN_ABI_VERSION_MAX]. The loader rejects anything else.init,start,stop: function pointers. NULL is permitted (a plugin can opt out of a phase).register_schemas: only read whenabi_version >= 2. ABI-1 plugins skip Phase B.
See ABI.md for the full contract, including the tail-append rule, the Phase-B-vs-Phase-D services availability matrix, the C++-ABI coupling note (std::string and prometheus-cpp are part of the contract — plugin and core must share toolchain), and the empty-source-sync invariant.
4. The four-phase plugin lifecycle
This is the single most important thing to validate. The lifecycle is implemented in lib/ProxySQL_PluginManager.cpp and driven from src/main.cpp's startup sequence.
startup shutdown
┌─────┐ Phase A Phase B Phase C Phase D Phase E ┌──────────┐
│main │ ─► load ──────► register_schemas ─► admin ────► init ──────► start ──────►│ workers │
└─────┘ (dlopen) (declare DDL) init (full (threads │ running │
(DDL run) services) spawn) └────┬─────┘
│
▼
...
│
shutdown
│
◄─── stop ─────── teardown
◄─── unload (dlclose)
| Phase | Driver | What runs | What's available to the plugin |
|---|---|---|---|
| A | proxysql_load_configured_plugins() |
dlopen(RTLD_NOW | RTLD_LOCAL), resolve proxysql_plugin_descriptor_v1, validate name + abi_version |
Nothing — the plugin's code does not run yet |
| B | proxysql_load_configured_plugins() invokes invoke_register_schemas_phase() |
Plugin's register_schemas callback runs. Plugins declare admin-schema tables and admin commands. |
Phase-B services struct: register_table, register_command, register_command_alias, log_message. DB getters return nullptr (admin not initialized yet). Query-hook registration refuses with a warning. |
| C | ProxySQL_Main_init_Admin_module() calls Admin::init() |
Admin module merges plugin-declared tables into tables_defs_{admin,config,stats} and runs check_and_build_standard_tables (single canonical DDL pass for both core + plugin tables) |
Plugin code does not run; admin is materializing the schema the plugin asked for in Phase B |
| D | proxysql_init_configured_plugins() |
Plugin's init callback runs |
Full services struct: live DB handles, register_query_hook works, get_prometheus_registry works |
| E | proxysql_start_configured_plugins() |
Plugin's start callback runs |
Full services. This is where the plugin spawns threads / opens listeners. |
| (run) | worker threads | Steady-state. Plugins respond to dispatch_admin_command and dispatch_query_hook calls from the core. |
Full services |
| stop | proxysql_stop_configured_plugins() |
Plugin's stop callback runs |
Full services |
| unload | manager destructor | dlclose after every plugin's stop returned |
Nothing |
Invariants worth verifying:
- stop pairs with init, not with start. If init succeeds but start fails, stop still runs. (See
lib/ProxySQL_PluginManager.cpp:548–576. Test:plugin_manager_unit-t.cpp:test_multi_plugin_start_failure_stops_started.) - Phase D writes commands_/hooks_; workers read them lock-free. Phase D MUST complete before any worker thread reads via
proxysql_has_configured_plugin_query_hook. If a future change moves listener startup before Phase D finishes, plain writes race plain reads. (Comment block atlib/ProxySQL_PluginManager.cpp:929–949.) - The manager pointer is published BEFORE Admin::init.
Admin::initreads tables viaproxysql_get_plugin_manager(). If the publish was deferred to after Phase D, the merge would see no plugin tables. (Comment block at the top ofproxysql_load_configured_plugins.) register_schemasis only dereferenced whenabi_version >= 2. A v1 plugin's struct ends afterstatus_json; reading past would be an out-of-bounds access. (Check atlib/ProxySQL_PluginManager.cpp:407.)- The descriptor struct is tail-extensible. A v3.x plugin compiled against ABI 2 still loads in a hypothetical future ABI 3 core (the core ignores the trailing fields it doesn't know; the plugin doesn't care). The reverse — a future plugin against an older core — is rejected at load time by the version check.
5. The mysqlx plugin as worked example
The mysqlx plugin lives entirely under plugins/mysqlx/. It builds as ProxySQL_MySQLX_Plugin.so and is loaded by the chassis at startup.
The state machine is in MysqlxSession (plugins/mysqlx/src/mysqlx_session.cpp, ~1,600 LOC). The 21-state enum Status drives a switch in handler() (line 187). For each Status value there's one handler_*() function. The state-to-handler mapping is in FILE_CHANGES.md area H.
A typical client flow:
client ProxySQL backend
│ │ │
├─── TCP connect ───────────────────────────────────►│ │
│ ├─ accept_new_connection │
│ │ → MysqlxSession in CONNECTING_CLIENT │
├─── CapabilitiesGet ──────────────────────────────► ├─ handler_connecting_client │
│ ◄────────────────── caps reply ────────────────────┤ → X_CAPABILITIES_GET → handler_capabilities_get
├─── CapabilitiesSet (tls=true, ──► ├─ handler_capabilities_set: │
│ compression=zstd_stream) │ parse caps, validate compression algo │
│ ◄──────────────────── Ok ─────────────────────────┤ │
│ ◄═══ TLS handshake ═══════════════════════════════►│ X_TLS_ACCEPT_INIT / _CONT / _DONE │
├─── AuthenticateStart (mysql41) ─────────────────► ├─ handler_auth_start │
│ ◄────────────── AuthenticateContinue ─────────────┤ (challenge sent) │
├─── AuthenticateContinue (scramble) ──────────────►├─ handler_auth_challenge_response: │
│ │ verify scramble (CRYPTO_memcmp) │
│ │ resolve_backend_target() ─── route ──────┤
│ ◄────────────────── AuthenticateOk ───────────────┤ │
│ │ WAITING_CLIENT_XMSG │
├─── StmtExecute ─────────────────────────────────► ├─ handler_waiting_client_msg │
│ │ → CONNECTING_SERVER if no backend yet │
│ │ → handler_connecting_server: connect + │
│ │ backend auth state machine ────────────►├─── X auth handshake
│ │ → forward_to_backend ───────────────────►├─── StmtExecute
│ │ │ ◄── Resultset rows
│ │ handler_waiting_server_msg │
│ │ → forward_frame_to_client │
│ │ → send_to_client_compressed │
│ ◄────────────── (zstd-compressed) Resultset ──────┤ │
│ │ WAITING_CLIENT_XMSG │
│ │ (loop) │
The mysqlx plugin demonstrates every chassis affordance:
- Phase B —
mysqlx_register_schemasdeclares 8 admin-schema tables (mysqlx_users,mysqlx_routes,mysqlx_backend_endpoints,mysqlx_variables, plus theirruntime_*mirrors andstats_mysqlx_*tables) and 16 admin commands (LOAD MYSQLX USERS TO RUNTIMEand the 7 cousins, plusSAVEvariants and aliases likeFROM MEMORY/FROM MEM/TO RUN). - Phase D —
mysqlx_initperforms disk-to-runtime sync of the mysqlx tables on first boot. - Phase E —
mysqlx_startclamps the thread-pool size, drives the listener reconciler fromruntime_mysqlx_routes, and spawns N worker threads. - Admin command dispatch — every
LOAD MYSQLX … TO RUNTIME/SAVElands as a callback onmysqlx_admin_schema.cpp:copy_table(). - Identity callbacks — each
MysqlxSessionis given anidentity_lookup_closure that calls back intoMysqlxConfigStore::resolve_identity()for the username the client sends.
If you can convince yourself the chassis can host the mysqlx plugin coherently, the chassis is in good shape.
6. Build & CI tier-flag wiring
PROXYSQL40=1 must propagate consistently through five Makefile layers. A mismatch silently changes the descriptor / services struct layouts between core and plugin — the link succeeds, the first dispatch corrupts memory.
top-level Makefile
│ PSQL40 := -DPROXYSQL40 (when PROXYSQL40=1)
│
├─► lib/Makefile (compiles ProxySQL_PluginManager.cpp into libproxysql.a)
├─► src/Makefile (links libproxysql.a)
├─► plugins/mysqlx/Makefile (compiles ProxySQL_MySQLX_Plugin.so)
└─► test/tap/tests/unit/Makefile (compiles plugin_*_unit-t / mysqlx_*_unit-t)
adds -DMYSQLX_TEST_BUILD on top
Verifications:
nm src/proxysql | grep -i ProxySQL_PluginManageris empty when built withoutPROXYSQL40=1.nm src/proxysql | grep -i ProxySQL_PluginManageris non-empty when built withPROXYSQLGENAI=1(which impliesPROXYSQL40=1).- The mysqlx plugin Makefile fails fast if libprotobuf is not 3.x (see
plugins/mysqlx/Makefile:38–51). Already saw this fire on CI in commit9f5ed235b— thecleanbuildpath triggered the check on a v3.0 box without libprotobuf-dev. Fixed by skipping the check onclean/cleanall. - CI workflow
.github/workflows/CI-mysqlx.ymlinvokesmakeinsideplugins/mysqlxwith all five tier flags explicitly:PROXYSQL40=1 PROXYSQL31=1 PROXYSQLFFTO=1 PROXYSQLTSDB=1 PROXYSQLGENAI=1. This was added in commitdf7e335e2after the tests/CI agent caught that the workflow was building the plugin with a different set of tier flags than the cachedsrc/proxysqlhad been built with — exactly the silent struct-layout-mismatch hazard.
7. Seams where consistency could break
These are the places where the incremental development pattern could have left contradictions — a comment that says one thing while the code does another, two paths that claim to do the same job, an #ifdef that forgot to flip somewhere. Each item has a one-line verification.
| # | Concern | Verification |
|---|---|---|
| 1 | The five Makefile layers all see the same tier flags | make clean && PROXYSQLGENAI=1 make succeeds; nm src/proxysql and nm plugins/mysqlx/ProxySQL_MySQLX_Plugin.so both contain ProxySQL_PluginManager symbols. |
| 2 | PROXYSQL40 macro gates every chassis source |
git grep -l 'class ProxySQL_PluginManager|register_schemas|invoke_register_schemas_phase' include lib src — every hit should be inside an #ifdef PROXYSQL40 block (or in a header that is itself wrapped). |
| 3 | The chassis is invisible to v3.x | make clean && make (no flags) — no chassis-related symbols in the binary. |
| 4 | The four-phase order in main.cpp matches the lifecycle invariant | src/main.cpp lines around 1570: LoadConfiguredPlugins → ProxySQL_Main_init_Admin_module → InitConfiguredPlugins → StartConfiguredPlugins. No reordering. |
| 5 | The descriptor register_schemas is gated by abi_version >= 2 |
lib/ProxySQL_PluginManager.cpp:407 — the read is inside if (descriptor->abi_version >= 2u). |
| 6 | materialize_plugin_tables() is gone (was a no-op disguised as load-bearing) |
git grep materialize_plugin_tables returns only the explanatory comment in Admin_Bootstrap.cpp:~1351. |
| 7 | Plugin getters in ProxySQL_Admin.cpp are gated |
proxysql_plugin_get_admindb/configdb/statsdb definitions are inside #ifdef PROXYSQL40 (line ~342). |
| 8 | Test-only forgery setters do not leak into production | `nm plugins/mysqlx/ProxySQL_MySQLX_Plugin.so |
| 9 | MysqlxConnection::reset() scrubs backend buffers between pool reuses |
plugins/mysqlx/src/mysqlx_connection.cpp:39–52: calls backend_ds_.clear_io_buffers() (added in commit 4bd4b462b). |
| 10 | Mysqlx_Thread::run() drains sessions on shutdown |
plugins/mysqlx/src/mysqlx_thread.cpp:run() post-loop walks sessions_ and calls shutdown_notify_client(). |
| 11 | inet_pton failure is fatal in start_connect() |
plugins/mysqlx/src/mysqlx_connection.cpp:53–67: returns -1 with state_=ERROR_STATE on parse failure. |
| 12 | groups.json lint passes locally |
python3 test/tap/groups/check_groups.py returns OK (no missing-from-groups, no built-but-unregistered). |
| 13 | All 32 plugin/mysqlx tests carry @proxysql_min_version:4.0 |
grep -E '^\s*"(mysqlx_|plugin_|test_mysqlx_)' test/tap/groups/groups.json | wc -l should be 32. (The total tag count grep -c '@proxysql_min_version:4.0' includes pre-existing GenAI/AI/MCP tests and is much higher — that's expected, not a bug.) |
| 14 | TLS_PASSTHROUGH (dead code) is gone | git grep TLS_PASSTHROUGH plugins/ returns nothing. |
| 15 | The dead #else /* !PROXYSQL40 */ branches in PluginManager are gone |
git grep '!PROXYSQL40' include lib — no inner #else branches inside whole-file PROXYSQL40 wraps. |
| 16 | The orphaned infras.lst and docker-compose-mysqlx.yml are gone |
ls test/tap/groups/mysqlx-e2e/infras.lst test/infra/docker-compose-mysqlx.yml returns "No such file or directory" for both. |
| 17 | g_active_plugin_manager_mutex is a std::shared_mutex (not std::mutex) |
grep 'std::shared_mutex g_active_plugin_manager_mutex' lib/ProxySQL_PluginManager.cpp. |
If all 17 of these check, the consistency concern is largely answered — the pieces line up.
8. Commit topology
The PR has ~150 commits, but they group naturally into five intent-bands. You don't need to review individually; you need to know which commits drove which design.
Band 1 — chassis foundation (early April)
The original design + initial implementation. These set the ABI shape.
7e1a12b8ffeat: add generic plugin ABI and loaderda7e18271fix: align plugin loader ABI and testsf13463d33feat(plugin-abi): Step 2.2 — four-phase plugin lifecyclecd15afdd1efeat: support plugin-owned admin tables and commands2430516600fix: reject conflicting plugin registrations
If you only read 5 commits, read these.
Band 2 — mysqlx baseline (early-to-mid April)
The X Protocol plugin built on top of the chassis. ~80 commits, mostly within plugins/mysqlx/. These are the "feature work" commits and are best reviewed by reading the result, not the patches — see FILE_CHANGES.md area A–L.
Band 3 — mysqlx hardening (mid-to-late April)
Bug fixes from running the plugin against a real MySQL 8.4. These are the small surgical commits — the kind you'd cherry-pick if you needed to backport a fix.
7e2f7828afix(mysqlx): make sync transactions atomic on execute() failurec78d7b859fix(mysqlx): reconcile bind-address changesdd131b0aafix(mysqlx): reconcile listeners at startup and on LOAD ROUTES TO RUNTIME3e8c3da9dfix(mysqlx): preserve backend TLS state past auth handshake82fe27f4bfix(mysqlx): sync empty source tables to overwrite stale rowse09908179fix(mysqlx): record stats on unreachable guard76aafdac6fix(mysqlx): harden check_connect() poll and getsockopt handlingbbe812251fix(mysqlx): reject auth without credential_lookup and release pooled fd
Band 4 — chassis hardening + invisibility audit (April 19–22)
After the original design landed, an audit found symbols leaking into v3.x and dead code paths. These commits clean that up.
3ba92815ffix(plugin-chassis): make chassis fully invisible in v3.x buildsab9d5a103fix(plugin-chassis): address deep-review findings6edde821cfix(glovars): forward-declare debug_level for direct header consumerse20876a45fix(tests): repair pre-existing mysqlx test build breakagef10411692fix(plugin-mgr): serialize lifecycle and always reset manager on stop
Band 5 — review-driven cleanup (this week, April 27)
The independent review of PR #5651 produced 19 findings. These commits address them.
9f5ed235bfix(build,test groups): unblock CI on plugin-chassis (B3 + #6 + lint)4bd4b462bfix(mysqlx): three blocking protocol/pool correctness bugs (B1 + B2 + parse)04bccec51chore(plugin-chassis): tighten gating, drop dead paths, gate forgery setters (#9 + #10 + #12 + #13)df7e335e2fix(ci,infra): pass PROXYSQL40 to plugin build, remove orphaned infra files (#5 + #4 + #18)55e90d1a7fix(plugin-chassis,mysqlx): chassis read-path scaling, graceful shutdown, hardening (#7 + #8 + #11 + #15 + #16 + #17)
For a complete walk through each finding and how it was addressed, see git log --grep="PR #5651" --since=2026-04-26.
9. Verification checklist
A reviewer who can tick each box has done a fair-quality review of this PR.
Build
make clean && make(no flags) succeeds on a v3.0-grade box.nm src/proxysql | grep ProxySQL_PluginManageris empty.make clean && PROXYSQLGENAI=1 makesucceeds.nm src/proxysql | grep ProxySQL_PluginManageris non-empty.cd plugins/mysqlx && PROXYSQL40=1 PROXYSQL31=1 PROXYSQLFFTO=1 PROXYSQLTSDB=1 PROXYSQLGENAI=1 makeproducesProxySQL_MySQLX_Plugin.so.nm plugins/mysqlx/ProxySQL_MySQLX_Plugin.so | grep -i 'visibility\|hidden'shows hidden visibility default; onlyproxysql_plugin_descriptor_v1exported.
ABI hygiene
- 32 plugin/mysqlx test entries carry
@proxysql_min_version:4.0; verify with the grep in §7 row 13. python3 test/tap/groups/lint_groups_json.pyreturns OK.python3 test/tap/groups/check_groups.pyreturns OK.
Tests
make build_tap_testssucceeds withPROXYSQLGENAI=1.- At least one of the larger unit tests passes:
cd test/tap/tests/unit && ./mysqlx_robustness_unit-tshould report 74/74. ./plugin_lifecycle_unit-t,./plugin_dispatch_unit-t,./plugin_query_hook_unit-tall green.
Sanity (manual)
- Start ProxySQL with
PROXYSQLGENAI=1build,plugins=("/path/to/ProxySQL_MySQLX_Plugin.so")in proxysql.cnf, and the mysqlx_users / mysqlx_routes tables populated. - Connect with
mysqlsh --mysqlx --uri user@host:6033(or X-Protocol port from your config). A handshake completes; aSELECT 1round-trips. SELECT * FROM stats_mysqlx_routesshows non-zero counters.SHUTDOWN— the client sees a clean disconnect, not a TCP RST. (This validatesshutdown_notify_client()from §7 row 10.)
Consistency seams (§7)
- All 17 rows from §7 verified.
If a reviewer ticks all of the above, this PR has been substantively reviewed.
Appendix: how this PR was assembled
For full transparency, the mysqlx plugin started as PR #5593 (ProtocolX branch) targeting v3.0 directly. The chassis started as PR #5651 (plugin-chassis branch) also targeting v3.0. PR #5593 was retargeted onto plugin-chassis and merged there, so the chassis PR (#5651) now carries both. This is why the commit history includes a mix of "chassis foundation" commits and "mysqlx feature/fix" commits — they were two PRs unified into one for review purposes, since the mysqlx plugin doesn't make sense without the chassis underneath it.
The independent review described in Band 5 above produced this guide as a deliverable.