DEV Community

Morgan Li
Morgan Li

Posted on

Shared Endpoint or Isolated Runtime: A Routing Rule for SQL Review Agents

At 09:14 a review bot comments on a migration that rewrites a forty-million-row payments table in staging. The comment itself is useful, but the request body includes the full CREATE TABLE statement plus three sample rows. Those rows came from a staging clone, and the inference endpoint sat on a shared server several other teams also called. Cost for that call was zero, which pleased finance, while the data-classification question remained completely unanswered.

SQL review agents fail in a quieter way than missing an index. They fail when query text, catalog fragments, and EXPLAIN output leave a trust boundary that nobody named. This article treats that boundary as a routing problem, not a model-quality contest. Two credible positions follow, then a decision rule a team can implement without inventing a new agent stack.

Why this is a routing debate, not a billing debate

Pull-request SQL already lives in git, so people assume the model prompt is equally public. That assumption collapses as soon as the bot adds live catalogs, row samples, connection hosts, or runtime plans. Those extras are what make comments specific, and they are also what change the classification of the payload.

A shared free endpoint can be the right sink for unclassified review traffic. An isolated runtime can be the right sink when the same bot starts attaching catalog facts. Mixing those sinks without a rule produces the Monday morning payload above. The rest of this piece stays on that split.

Position A: Put the review model on a shared free endpoint

Position A says iteration speed is the scarce resource during the first months of a SQL review bot. Engineers will not tune prompts, comment templates, or severity labels if every dry run needs a purchase order. A shared endpoint with free model access removes that stall, so the team can measure false positives on real pull requests.

Evidence for this position is operational rather than rhetorical. Count how many review payloads in a week contain only SQL that already appears in the pull request, with no live catalog, no row samples, and no hostnames. If that share is high, a shared server is carrying low-sensitivity work. The cost of isolating that work is delay, not safety.

Position A also argues that comment quality and data egress are separate controls. Redaction and allow-lists can sit in front of any endpoint. If those controls are tested, the physical location of the model is a capacity choice. Teams that already publish DDL in public repositories have a weaker case for treating every review prompt as secret.

Position B: Keep inference inside an isolated runtime

Position B says SQL review is not generic chat. The dangerous object is the concatenation of schema, predicates, and samples, even when each piece looks dull alone. A shared server used by several teams expands the set of people, processes, and logs that can see that concatenation.

Evidence for this position shows up in payload diffs, not in model leaderboards. Compare the bot's request body with the files in the pull request. Any field that does not appear in git is an egress candidate: pg_stats excerpts, EXPLAIN JSON, column comments from a live catalog, or “example rows to help the model.” Those fields are why isolated inference exists.

Position B further notes that free shared capacity does not change retention, subprocessors, or prompt-log access. A review agent that posts in GitHub still sent the prompt somewhere else first. If the somewhere else is a multi-tenant server, the threat model includes operator access and noisy-neighbor logs, not only model weights.

Where a free shared server actually participates

The useful compromise is not “always shared” or “always private.” It is a classifier in front of the model call. Unclassified PR-only SQL may use a shared free endpoint so prompt work does not wait on procurement. Classified payloads never take that path.

MonkeyCode's free model access and free server option can fill the unclassified slot in that design, which is the only slot this article assigns them. Disclosure: This article was prepared as part of MonkeyCode's product outreach. They are not a substitute for isolation when the payload includes catalog extracts, samples, or connection metadata. Treat both offerings as availability claims, not as a graded security certification.

Artifact: classify the payload before you choose an endpoint

The following classifier is a proposal, not a production security review. It encodes the split above as explicit fields a SQL review agent already tends to collect. Label it unexecuted until your team runs the tests in the next section against recorded payloads.

from dataclasses import dataclass, field
from enum import Enum
from typing import Any

class Sink(str, Enum):
    SHARED_FREE = "shared_free"
    ISOLATED = "isolated"
    REFUSE = "refuse"

SENSITIVE_MARKERS = (
    "password",
    "secret",
    "aws_access",
    "connection_string",
    "copy (",
    "pg_dump",
)

@dataclass
class ReviewPayload:
    sql_from_pr: str
    live_catalog: dict[str, Any] = field(default_factory=dict)
    explain_json: dict[str, Any] = field(default_factory=dict)
    sample_rows: list[dict[str, Any]] = field(default_factory=list)
    system_class: str = "internal"  # public | internal | confidential | restricted
    hosts: list[str] = field(default_factory=list)

def contains_secret_marker(text: str) -> bool:
    lowered = text.lower()
    return any(marker in lowered for marker in SENSITIVE_MARKERS)

def route_sql_review(payload: ReviewPayload) -> Sink:
    blob = payload.sql_from_pr
    if contains_secret_marker(blob):
        return Sink.REFUSE
    if payload.system_class in {"confidential", "restricted"}:
        return Sink.ISOLATED
    extras = (
        bool(payload.live_catalog)
        or bool(payload.explain_json)
        or bool(payload.sample_rows)
        or bool(payload.hosts)
    )
    if extras:
        return Sink.ISOLATED
    if payload.system_class == "public" or not extras:
        return Sink.SHARED_FREE
    return Sink.ISOLATED
Enter fullscreen mode Exit fullscreen mode

A second artifact is the decision table the classifier implements. Keep this table in the bot repository next to the prompt templates, so routing changes require a reviewed diff.

Payload contents System class Allowed sink Notes
SQL already in the PR, no extras public or internal shared free Prompt iteration path
SQL in the PR plus live catalog any isolated Catalog is not git
SQL plus EXPLAIN JSON any isolated Plans leak predicates and row estimates
Any sample rows any isolated Rows are data, not schema
Hostnames or connection strings any refuse or isolated Prefer refuse
Secret markers in SQL text any refuse Do not “redact and send” by default
confidential or restricted systems any isolated Class beats convenience

Numbered workflow on a throwaway repository

  1. Record twenty real review payloads from a non-production bot, including every JSON field the agent currently sends. Store them as files, not in chat history, so the classifier can run twice.
  2. Label each file with system class and with a boolean for extras that do not appear in the pull request. Do this labeling by hand for the first week; automation comes after disagreement rate drops.
  3. Run route_sql_review over the corpus and print a three-column tally: shared_free, isolated, refuse. If refuse is zero, your marker list is probably too weak.
  4. Bind only the shared_free slice to a free shared server, and keep the other slices on an isolated runtime or drop them. Do not “temporarily” send isolated traffic to the shared sink to save a queue.
  5. Re-read five shared_free comments on later pull requests and check whether the bot quietly reintroduced extras. If a comment cites a live index the PR never mentioned, the classifier leaked around the route.

A minimal dry-run command looks like this. Adjust paths; do not paste production schema into the sample files.

python - <<'PY'
from pathlib import Path
import json
from review_route import ReviewPayload, route_sql_review

for path in sorted(Path("payloads").glob("*.json")):
    raw = json.loads(path.read_text())
    sink = route_sql_review(ReviewPayload(**raw))
    print(f"{path.name}\t{sink.value}")
PY
Enter fullscreen mode Exit fullscreen mode

Expected output is a stable mapping from filename to sink. A test plan with four fixtures is enough to prevent silent drift when someone adds “helpful” catalog context next quarter.

# labeled tests; unexecuted until you wire pytest
def test_pr_only_sql_may_use_shared_free():
    payload = ReviewPayload(sql_from_pr="ALTER TABLE orders ADD COLUMN note text;")
    assert route_sql_review(payload).value == "shared_free"

def test_sample_rows_force_isolated():
    payload = ReviewPayload(
        sql_from_pr="SELECT * FROM orders WHERE id = 1",
        sample_rows=[{"id": 1, "email": "dev@example.com"}],
    )
    assert route_sql_review(payload).value == "isolated"

def test_explain_json_force_isolated():
    payload = ReviewPayload(
        sql_from_pr="SELECT 1",
        explain_json={"Plan": {"Node Type": "Seq Scan", "Relation Name": "orders"}},
    )
    assert route_sql_review(payload).value == "isolated"

def test_secret_marker_refuses():
    payload = ReviewPayload(sql_from_pr="-- password = hunter2\nSELECT 1")
    assert route_sql_review(payload).value == "refuse"
Enter fullscreen mode Exit fullscreen mode

Evidence a team can collect without a vendor bake-off

Shared-endpoint advocates should publish two numbers after one week: share of payloads that are PR-only, and comment precision on that slice. Isolated-runtime advocates should publish two different numbers: count of extra fields not present in git, and count of systems classed confidential. Those four numbers decide the mix. Model brand names do not.

If PR-only share is below one third, Position A is arguing from a workload you do not have. If extra-field count is near zero, Position B is paying isolation rent on chat that already matched the pull request. Re-measure when someone adds catalog tools, because that commit changes the mix overnight.

Limitations, and who should not use the shared path

This routing rule does not encrypt prompts, negotiate a business associate agreement, or prove a vendor's log retention. It only stops unclassified and classified traffic from sharing a sink by accident. Redaction is incomplete by construction: SQL predicates can identify people without containing a column named email.

Do not use the shared free path for health, payments, or government schemas, even when the SQL is “just a migration.” Do not use it when sample rows exist, including fake-looking staging rows that were copied from production last year. Do not use it as a workaround when the isolated runtime is slow. Queue delay is cheaper than an untracked catalog dump.

Teams without a data-classification label should not default to shared. Missing labels are not “public.” They are unknown, and unknown routes to isolated or refuse. The classifier above treats confidential and restricted as isolated regardless of extras, which is intentional and not a performance bug.

The decision rule

Use a shared free endpoint only when every field in the model request already appears in the pull request, the system class is public or internal, and secret markers are absent. Otherwise keep inference isolated, or refuse the call. Re-run the corpus tally whenever the agent gains a new tool that can read catalogs, plans, or rows.

That rule is deliberately boring. SQL review agents already accumulate if-statements around severity and style; they need one more around egress. Cost pressure belongs in the unclassified slice. Schema and samples do not become cheaper when the server is free.

If you already operate a SQL review bot, paste the decision table into the repository and fail CI when a new prompt field is added without a sink update. The table is the product of this debate; the endpoint you bind to shared_free is a later, narrower choice.

Top comments (0)