Add SQL support #71

Closed
opened 2026-08-15 23:00:24 +00:00 by coilysiren · 2 comments
Owner
No description provided.
Member

Engineer read by Angie (claude seat), 2026-08-19. This issue is autonomy/async-consult role/director with an empty body, so it is not mine to build or to scope. Recording the technical context the consult would need, because the answer is less like #70 than the neighbouring titles suggest.

SQL is not a near neighbour of the graphql epic

Both read as "another upstream flavour". They are not the same size.

#70 adds a node to a grammar that already produces the request it needs. A GraphQL call is an HTTP POST with a JSON body, so the whole existing engine underneath it (base-url, auth, rate limit, cache, confirmations, pins, restrictions, fail-when) applies unchanged.

SQL has no transport here to attach to. Measured across both trees:

  • umbra at v0.154.0: grep -rn "database/sql\|pgx\|sqlite\|mysql" over the whole module returns nothing. The guarded-execution engine is http/opcore, and its unit of work is Descriptor plus an HTTP request.
  • mcp-beaver on main: the only transport in internal/mcpserver is net/http.

So a sql grant is not a new node on an existing request. It is a second execution engine beside the HTTP one, with its own connection lifecycle, pooling, credential shape, and cancellation. Every guardfile control listed above would need a second meaning or an explicit refusal.

The questions the consult probably turns on

Offered as the engineering shape of the decision, not as a recommendation, since scope here is the director's.

  • Is the ask really SQL, or is it a database an HTTP layer already fronts? PostgREST, Hasura, Supabase, and Datasette all put a database behind HTTP, and every one of those is expressible as an ordinary guardfile today with no runtime change. If that covers the real need, this issue closes without code.
  • What is the deny-by-absence claim for a query language? The README's claim is "everything you declared works, nothing else is reachable", and #84 is already about proving the second half. Grants enumerate operations. A SQL surface either enumerates fixed statements (which is a stored-query feature, closer to #70 than to "SQL support") or accepts caller SQL, which is the opposite of deny-by-absence and would be this product's first reachable-by-construction surface.
  • Which repo owns it? By the same boundary that puts the graphql grammar in umbra (#70 comment above), a sql grant type is umbra's guarded-execution half, and mcp-beaver would own only the projection of a result set into tool content. But umbra taking a database driver is a materially bigger call than umbra taking encoding/xml was, and that is a question for umbra's own charter rather than an implementation detail of this issue.

Status

I did not write a body, retitle, relabel, or scope this. It stays where it is at priority/P4 autonomy/async-consult awaiting the human consult its label names. The read above is meant to make that consult cheaper, not to preempt it.

**Engineer read by Angie (`claude` seat), 2026-08-19.** This issue is `autonomy/async-consult` `role/director` with an empty body, so it is not mine to build or to scope. Recording the technical context the consult would need, because the answer is less like `#70` than the neighbouring titles suggest. ## SQL is not a near neighbour of the graphql epic Both read as "another upstream flavour". They are not the same size. `#70` adds a node to a grammar that already produces the request it needs. A GraphQL call is an HTTP POST with a JSON body, so the whole existing engine underneath it (base-url, auth, rate limit, cache, confirmations, pins, restrictions, `fail-when`) applies unchanged. **SQL has no transport here to attach to.** Measured across both trees: * umbra at `v0.154.0`: `grep -rn "database/sql\|pgx\|sqlite\|mysql"` over the whole module returns nothing. The guarded-execution engine is `http/opcore`, and its unit of work is `Descriptor` plus an HTTP request. * mcp-beaver on `main`: the only transport in `internal/mcpserver` is `net/http`. So a `sql` grant is not a new node on an existing request. It is a second execution engine beside the HTTP one, with its own connection lifecycle, pooling, credential shape, and cancellation. Every guardfile control listed above would need a second meaning or an explicit refusal. ## The questions the consult probably turns on Offered as the engineering shape of the decision, not as a recommendation, since scope here is the director's. * **Is the ask really SQL, or is it a database an HTTP layer already fronts?** PostgREST, Hasura, Supabase, and Datasette all put a database behind HTTP, and every one of those is expressible as an ordinary guardfile today with no runtime change. If that covers the real need, this issue closes without code. * **What is the deny-by-absence claim for a query language?** The README's claim is "everything you declared works, nothing else is reachable", and `#84` is already about proving the second half. Grants enumerate operations. A SQL surface either enumerates fixed statements (which is a stored-query feature, closer to `#70` than to "SQL support") or accepts caller SQL, which is the opposite of deny-by-absence and would be this product's first reachable-by-construction surface. * **Which repo owns it?** By the same boundary that puts the graphql grammar in umbra (`#70` comment above), a `sql` grant type is umbra's guarded-execution half, and mcp-beaver would own only the projection of a result set into tool content. But umbra taking a database driver is a materially bigger call than umbra taking `encoding/xml` was, and that is a question for umbra's own charter rather than an implementation detail of this issue. ## Status I did not write a body, retitle, relabel, or scope this. It stays where it is at `priority/P4` `autonomy/async-consult` awaiting the human consult its label names. The read above is meant to make that consult cheaper, not to preempt it.
Member

Delivered. Closing. Angie (engineer, claude seat).

This was an empty-bodied autonomy/async-consult role/director issue. Kai's "let's teach umbra graphql and sql" was that consult, so the design questions I recorded above got answered rather than deferred.

What shipped

  • umbra#308 (8b098f7, released v0.159.0) - database <driver> { value ... } at wrap level, sql { statement ...; param ...; max-rows ... } per grant.
  • mcp-beaver 5802a9a - pin umbra v0.159.0. No code change: params project at LocationBody like graphql variables.
  • mcp-beaver 178ffcc - register the pgx driver, so a sql grant runs rather than only lints.

The design question this issue turned on

I flagged it above: a query language is the one place deny-by-absence can quietly break, because a tool taking a SQL string reaches every table its credential can see. That would have been this product's first reachable-by-construction surface.

Named statements with bound parameters instead. The guardfile authors each statement; the caller supplies only declared parameters, which bind as placeholders and are never interpolated. Same shape graphql took for documents, and the same shape grants have always had.

Fails closed at build on stacked statements, a placeholder set that is not $1..$N, a count disagreeing with the params, a non-binding param type, a sql block beside any HTTP construct, a grant with no wrap-level database, and a reading verb whose statement mutates. That last one walks a leading WITH for a hidden DELETE, so a CTE cannot smuggle a write behind can list.

Zero dependency growth in umbra

umbra opens the named database/sql driver and imports none, so it stays policy-free and driver-agnostic. The choice of reachable databases is made in the consumer binary. mcp-beaver links Postgres only (pgx), on Kai's call: it is what the fleet runs, and every extra driver is image weight for a database nobody has. MySQL or SQLite later is one import.

Verified, and not

Verified through a served mcp-beaver: a sql guardfile's tools/list offers exactly the declared params with the statement absent, and a call now reaches a real pgx connection attempt rather than an unregistered-driver error. umbra's own end-to-end ran against real SQLite through database/sql, where a bounded read reported truncated, ada'; DROP TABLE orders; -- bound as a value and matched nothing, and the table was intact afterwards.

Not verified: a successful round trip against a real Postgres. Docker's daemon is down on this host and the running instances are in the sirens namespaces, where connecting an agent is a DevOps action rather than an engineering one. Worth one real call before a sql guardfile ships to a cluster.

No deployed consumer yet

Nothing in coilyco-bridge/deploy declares a sql grant. This is capability ahead of demand, which is how it was asked for. Reopening is cheap if "SQL support" meant something wider than a guarded query surface.

**Delivered. Closing.** Angie (engineer, `claude` seat). This was an empty-bodied `autonomy/async-consult` `role/director` issue. Kai's "let's teach umbra graphql and sql" **was** that consult, so the design questions I recorded above got answered rather than deferred. ## What shipped * **`umbra#308`** (`8b098f7`, released `v0.159.0`) - `database <driver> { value ... }` at wrap level, `sql { statement ...; param ...; max-rows ... }` per grant. * **mcp-beaver `5802a9a`** - pin umbra `v0.159.0`. No code change: params project at `LocationBody` like graphql variables. * **mcp-beaver `178ffcc`** - register the `pgx` driver, so a `sql` grant runs rather than only lints. ## The design question this issue turned on I flagged it above: a query language is the one place deny-by-absence can quietly break, because a tool taking a SQL string reaches every table its credential can see. That would have been this product's first reachable-by-construction surface. **Named statements with bound parameters** instead. The guardfile authors each statement; the caller supplies only declared parameters, which bind as placeholders and are never interpolated. Same shape `graphql` took for documents, and the same shape grants have always had. Fails closed at build on stacked statements, a placeholder set that is not `$1..$N`, a count disagreeing with the params, a non-binding param type, a `sql` block beside any HTTP construct, a grant with no wrap-level `database`, and a reading verb whose statement mutates. That last one walks a leading `WITH` for a hidden `DELETE`, so a CTE cannot smuggle a write behind `can list`. ## Zero dependency growth in umbra umbra opens the named `database/sql` driver and imports none, so it stays policy-free and driver-agnostic. The choice of reachable databases is made in the consumer binary. mcp-beaver links **Postgres only** (`pgx`), on Kai's call: it is what the fleet runs, and every extra driver is image weight for a database nobody has. MySQL or SQLite later is one import. ## Verified, and not Verified through a served mcp-beaver: a sql guardfile's `tools/list` offers exactly the declared params with the statement absent, and a call now reaches a real pgx connection attempt rather than an unregistered-driver error. umbra's own end-to-end ran against real SQLite through `database/sql`, where a bounded read reported `truncated`, `ada'; DROP TABLE orders; --` bound as a value and matched nothing, and the table was intact afterwards. **Not verified: a successful round trip against a real Postgres.** Docker's daemon is down on this host and the running instances are in the sirens namespaces, where connecting an agent is a DevOps action rather than an engineering one. Worth one real call before a sql guardfile ships to a cluster. ## No deployed consumer yet Nothing in `coilyco-bridge/deploy` declares a `sql` grant. This is capability ahead of demand, which is how it was asked for. Reopening is cheap if "SQL support" meant something wider than a guarded query surface.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/mcp-beaver#71
No description provided.