4.9 KiB
Design: single-react-mysql-readonly-tool
Architecture
MysqlToolRequest + ToolCallRequestEnvelope
|
v
MysqlToolAdapter
- parse typed request
- MysqlSqlValidator -> MysqlQueryPlan
- ToolBoundary.execute
|
+--> MysqlReadOnlyExecutor (JDBC or isolated fake)
| - read-only connection
| - PreparedStatement params
| - timeout/max rows/cancel
|
+--> MysqlResultProjector
- bounded cells/rows/bytes
- sensitive column redaction
- MysqlToolResult JSON
The adapter validates the typed request before entering ToolBoundary. Once validation succeeds, the executor and projector run inside the existing canonical lifecycle. Validation failures have no canonical record because no database call is authorized; execution/projection failures after PROJECTING become canonical ERROR records.
Configuration model
MysqlDataSourceDefinition is an immutable logical definition:
- logical ID
- JDBC URL, username and password supplied by configuration/Secret
- default schema
- exact schema/table/column allowlist
- query timeout seconds, max rows, max cell characters and max result bytes
The Agent sees only the logical ID. The stage does not create a dynamic datasource registry or reuse the application persistence datasource. A caller supplies a Map<String, DataSource> to the JDBC executor, allowing isolated test data sources and later production wiring.
SQL validator
MysqlSqlValidator uses CCJSqlParserUtil.parseStatements and rejects unless there is exactly one Select with a PlainSelect body and no CTE/compound body. It rejects SubSelect, SetOperationList, ValuesStatement, ForUpdate, metadata statements, wildcard projection except COUNT(*), unsupported joins, unsupported functions, and unknown table/from items.
The validator builds an alias map for every base table and checks every table/column reference in select items, joins, where, group by, having and order by. Unqualified columns must resolve to exactly one allowlisted table; qualified columns must resolve through a declared alias/table and exact allowlist. It counts JdbcParameter nodes and requires an exact match with params.
The function allowlist starts with COUNT, SUM, AVG, MIN, and MAX. COUNT(*) is represented as a function-level exception; a projection * or table.* is rejected. Any parser exception or policy ambiguity produces a MysqlSecurityException before execution.
JDBC executor
JdbcMysqlReadOnlyExecutor resolves the validated logical ID to a caller-supplied DataSource, opens a connection, calls setReadOnly(true), prepares the validated SQL, binds parameters in order, applies setQueryTimeout and setMaxRows, and reads column labels plus structured values. It checks RunContext cancellation/deadline while iterating and cancels/closes the statement on abort.
The executor returns a Harness-only raw record. It never returns a JDBC connection, SQL exception details, credentials or stack traces to the Agent. JDBC errors are mapped by ToolBoundary to a stable safe error.
Result projection
MysqlResultProjector parses only the executor raw record and emits MysqlToolResult:
- immutable ordered columns
- bounded ordered rows
- cell values converted to JSON-safe scalar/text values
- redaction for sensitive column names (
password,token,secret,api_key, etc.) returned_countequal to projected rowstruncated=truefor row/cell/byte removalNO_EVIDENCEfor a successful empty result
Projection never exposes raw JDBC metadata, connection coordinates, internal error messages or the unbounded raw result.
Script safety cleanup
scripts/query_mysql.py will require SUPERBIZ_MYSQL_HOST, SUPERBIZ_MYSQL_PORT, SUPERBIZ_MYSQL_USERNAME, SUPERBIZ_MYSQL_PASSWORD and SUPERBIZ_MYSQL_DATABASE (with no external defaults), accept only one SELECT statement, and reject all non-SELECT/metadata/write SQL before opening a connection. It will call rollback/close defensively and remove the interactive write path.
Interface and compatibility impact
- L2 internal Harness classes plus the JSqlParser build dependency.
- No public HTTP/SSE/Chat contract changes.
- No changes to legacy tools, recorder, JPA entities, or current application datasource.
- The main capability spec will be synchronized after archive.
Risks and mitigations
- JSqlParser AST API drift: lock version 4.6 and run parser security fixtures.
- Alias/column ambiguity: fail closed rather than guessing.
- JDBC cancellation is driver-dependent: check Run state before/while iteration and call
Statement.cancel()on abort. - Sensitive result values: redact by column name before JSON serialization and keep raw only in canonical Harness storage.
- A malicious query can still be expensive within SELECT: timeout, max rows, read-only connection and configured byte limits remain mandatory.