A working, clean-room Snowflake SQL API connector for Rust and coding agents. It authenticates to live Snowflake accounts (key-pair JWT, PAT, or OAuth bearer), submits SQL over the SQL API with no ODBC, no JDBC, and no Tokio, and it both reads and writes live data. On the read side it streams back typed query results, catalog discovery, and lineage; on the write side
query writeruns INSERT, MERGE, UPDATE, DELETE, COPY INTO, and PUT through a dry-run to confirm safety ladder. Results come back as deterministic JSON ortoon. It ships an agent-ergonomic CLI (franken-snowflake/fsnow), an optional MCP server, a TUI, and deterministic tests that need no warehouse. The live SQL transport is compiled in with thelivefeature.
Snowflake ships official drivers for Go, JDBC, .NET, Node.js, ODBC, PHP, and
Python. It does not ship one for Rust. A Rust service, or a coding agent that
wants to query Snowflake without standing up a Python sidecar, is left with ODBC
bridges, JDBC over JNI, or third-party crates whose dependency graphs pull in
Tokio, reqwest, and a transitive forest no one audited.
For an agent the situation is worse. Raw SQL plus scattered secrets is a poor interface. There is no machine-readable capability list, no way to ask "what data exists here," no deterministic JSON contract, and no guardrail against running an expensive unbounded scan by accident.
franken_snowflake talks to the Snowflake SQL API
directly over HTTPS, with no ODBC, no JDBC, and no third-party Snowflake crate.
It is built on Asupersync, a
spec-first, cancel-correct, capability-secure async runtime, so networking,
cancellation, retry budgets, and deterministic tests come from one audited
foundation instead of the Tokio ecosystem.
The interface is designed for agents first: a deterministic versioned JSON envelope on every command, a self-describing capability registry, a binary-embedded handbook, exact next-command suggestions inside errors, stable exit codes, and an optional MCP server that turns every read verb into a callable tool. A deterministic testkit exercises the protocol against a mock SQL API server, so the contracts are proven with no warehouse before any live credential exists.
| Capability | What you get |
|---|---|
| Rust-first, memory-safe | forbid(unsafe_code) workspace-wide; lints deny unwrap/expect/panic/todo/dbg! |
| No hidden async runtime | Built on Asupersync; production crates forbid Tokio, reqwest, hyper, axum, tower, sqlx, diesel, sea-orm |
| Agent-ergonomic by default | Deterministic --json (and token-efficient --toon), capabilities, agent-handbook, onboard, did_you_mean, stable exit codes |
| Callable as a tool | Optional mcp serve exposing the same handlers and envelope contract over stdio or HTTP |
| Deterministic tests | A mock SQL API server and a codec lane under a lab runtime exercise the contracts with no warehouse |
| Never a fixture posing as live data | data_source provenance on every envelope; the live path refuses cleanly when credentials are absent |
| Safe writes | query write runs DML, COPY INTO, and PUT directly once a profile sets WRITE_ENABLED; --dry-run previews and binds a (profile, SQL) confirmation token, WRITE_REQUIRE_CONFIRM re-arms that ceremony, and DDL needs a separate opt-in |
| Secrets stay secret | No secret in config, Debug, JSON, or panic text; a compile-time leak gate enforces it |
| Auditable after the fact | Content-addressed query receipts and an append-only audit log |
The commands below cover discovery, self-description, and offline planning, and
they need no credentials. Read commands emit a deterministic JSON envelope on
stdout (--json, the default) or a token-efficient --toon encoding.
Diagnostics go to stderr. An empty-but-valid result is exit 0 with an empty
payload, never a non-zero exit.
# One call that orients an agent: capabilities, exit codes, first commands, health.
fsnow onboard --json
# The complete machine-readable command registry.
fsnow capabilities --json
# Local readiness checks (no network).
fsnow doctor --json
# Validate a profile's shape and the env-var handles it references (no network).
fsnow profile validate demo-prod --json
# Ask the connector to describe a filter operator as JSON Schema 2020-12.
fsnow dataset describe-operator between --jsonschema
# Validate and explain a query plan without submitting it.
fsnow query plan --profile demo-prod --sql "select * from events limit 10" --json
# Render catalog lineage as Mermaid (live source requires the `live` feature + credentials).
fsnow catalog graph demo-prod --database ANALYTICS --schema PUBLIC --mermaidWith the live feature compiled in and the profile's credential handles
exported, the same binary reads from and writes to the live account:
# Read: run a single statement against the live account.
fsnow query run --profile demo-prod --sql "select current_version()" --json
# Write: once the profile sets WRITE_ENABLED, a bare `query write` executes the
# mutation directly and returns the live execution receipt.
fsnow query write --profile demo-prod --sql "insert into events (id) select 1" --json
# Optional preview: --dry-run executes nothing and returns the plan plus a
# confirmation token, without running the statement.
fsnow query write --profile demo-prod --sql "insert into events (id) select 1" --dry-run --jsonfsnow is the short alias for the canonical franken-snowflake binary. Both
share one entry point and one contract, so every example works under either
name.
Clean-room and Rust-first. No ODBC, no JDBC bridge, no vendored or third-party Snowflake crate. The connector speaks the documented SQL API over HTTPS. Third-party Rust Snowflake crates may be studied as read-only inspiration, but they are never copied or added as production dependencies. The authoritative behavioral sources are Snowflake's documentation, live protocol observations, and the project's own conformance fixtures.
Asupersync-native. Each hard part of the connector maps to a concrete
Asupersync primitive: a four-valued Outcome (Ok / Err / Cancelled /
Panicked), a structured CancelReason, a Budget with a cost quota,
capability-row narrowing so each path runs with only the capabilities it needs,
bracket for orphan-free statement cancellation, and a deterministic lab
runtime with DPOR race coverage for the cancellation and retry paths. The
HTTP/TLS transport, gzip, and retries also come from Asupersync, not Tokio.
Deterministic tests. The protocol is exercised without a warehouse. Two lanes carry the proof: a deterministic codec lane over a virtual TCP transport under the lab runtime, and an integration lane against a mock SQL API server. Live tests are opt-in and emit a typed skip or refusal when credentials are absent, rather than silently passing.
Agent-ergonomic JSON contracts. Every command returns a versioned envelope
with a typed outcome_kind, a data_source provenance field, a stable error
code from a central registry that gives each code a default recovery path,
did_you_mean suggestions, and a documented exit-code scheme where an
empty-but-valid result is exit 0. The CLI and the MCP server share the exact
same handlers, so the two surfaces cannot drift into two contracts.
Forbid-unsafe and deny-panic. The workspace sets unsafe_code = "forbid"
and denies clippy::unwrap_used, expect_used, panic, todo, and
dbg_macro. Every crate inherits the policy through [lints] workspace = true,
and the policy is verified to actually fail a build or clippy run.
Safe writes, redaction, guardrails, and budgets. Secrets never appear in
config, Debug, JSON output, or panic text; a compile-time gate fails the build
if a credential-shaped field derives Debug. Reads run with read-only
capabilities, while writes are gated behind a per-profile WRITE_ENABLED opt-in
and execute directly once enabled; --dry-run previews and binds a confirmation
token to the exact statement, and WRITE_REQUIRE_CONFIRM makes that ceremony
mandatory for cautious profiles. Cost and safety
guardrails bound work before it is dispatched, and result rows are capped into a
response envelope with an explicit truncated flag so an agent never receives
an unbounded payload by surprise.
Deterministic testkit. A shared golden framework, a JSON-line logger, a deterministic clock, and a canary guard back the proof lanes. Goldens are newline-pinned and CRLF-safe so they compare identically across platforms.
franken_snowflake runs real read and write statements against live Snowflake
accounts, and ships deterministic tests that exercise the same contracts offline
for fast CI. The table below sets it against the alternatives.
| franken_snowflake | Official drivers (Python / Go / JDBC / ...) | Third-party Rust crates | ODBC / JDBC bridge | |
|---|---|---|---|---|
| Language / runtime | Rust on Asupersync | Per language | Rust on Tokio | Native lib plus bridge |
| Hidden Tokio/reqwest graph | None, by policy | n/a | Usually | n/a |
| Agent JSON contract plus MCP | First-class | No | No | No |
| Deterministic tests, no warehouse | Yes | Varies | Rare | No |
Safe writes (WRITE_ENABLED gate, optional confirm) |
Built-in | No | No | No |
| Secret-leak compile gate | Yes | No | No | No |
| Live read and write against a Snowflake account | Yes (--features live) |
Yes | Varies | Yes |
If you are not working in Rust, an official driver is the natural choice.
franken_snowflake exists for the Rust-first, agent-first, Tokio-free niche the
official drivers do not cover.
Install with the one-liner below, or build from source explicitly. The installers download prepared GitHub release binaries by default; they do not fall back to cargo builds when a release asset is missing.
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/franken_snowflake/main/install.sh | bashirm https://raw.githubusercontent.com/Dicklesworthstone/franken_snowflake/main/install.ps1 | iexThe installer accepts these flags (pass after bash -s -- for the curl form):
| Flag | Effect |
|---|---|
--version <v> |
Install a specific released version instead of the latest |
--dest <dir> |
Install into a chosen directory |
--system |
Install system-wide rather than per-user |
--easy-mode |
Guided, prompt-friendly install for newcomers |
--verify |
Run a post-install self-test after checksum/signature verification |
--from-source |
Developer-only: build from source instead of downloading a prepared release binary |
--live |
Source-build option: compile the live feature when combined with --from-source |
--quiet |
Suppress non-error output |
--no-gum |
Plain output with no styled prompts |
--force |
Overwrite an existing install |
Release binaries are built with the published live/MCP feature set. Credentials are still runtime-gated; the binary refuses live operations cleanly when the selected profile or environment does not provide credential handles.
To build the live-capable binary from source in one shot, pass both
--from-source and --live through the pipe:
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/franken_snowflake/main/install.sh | bash -s -- --from-source --live
--from-sourcebuilds from a fresh standalone clone: the FrankenSuite dependencies resolve from crates.io, so no local sibling checkout is required.
On Windows the irm ... | iex one-liner cannot forward arguments, so download
the script first and invoke it with the matching source-build switches:
irm https://raw.githubusercontent.com/Dicklesworthstone/franken_snowflake/main/install.ps1 -OutFile install.ps1
./install.ps1 -FromSource -Livegit clone https://github.com/Dicklesworthstone/franken_snowflake
cd franken_snowflake
# Build the agent CLI with the default features (toon output on, live off).
cargo build --release -p franken-snowflake-cli
# Or install the binaries (franken-snowflake and fsnow) onto your PATH.
cargo install --path crates/franken-snowflake-cliThe default build compiles the deterministic agent surface with the toon
output mode on; the mcp and live features are off. Turn them on by feature:
# Add the MCP server surface.
cargo build --release -p franken-snowflake-cli --features mcp
# Add live Snowflake SQL API transport for reads and writes (credential-gated at runtime).
cargo build --release -p franken-snowflake-cli --features live
# Everything.
cargo build --release -p franken-snowflake-cli --features mcp,liveBoth binary names install from the same crate: franken-snowflake is canonical
and fsnow is the short alias. The whole stack requires a nightly Rust
toolchain (edition 2024), inherited from the FrankenSQLite, sqlmodel, and
Asupersync dependency set; the pinned toolchain lives in rust-toolchain.toml.
-
Build the CLI.
cargo build --release -p franken-snowflake-cli
-
Orient yourself in one call.
./target/release/fsnow onboard --json
-
Check local readiness (no network).
./target/release/fsnow doctor --json
-
Plan a query offline. The planner validates the statement, refuses mutations and multi-statement input, and returns a typed envelope without contacting Snowflake.
./target/release/fsnow query plan --profile demo-prod \ --sql "select id, created_at from events limit 100" --json -
Run a live read. Rebuild with the
livefeature, export the profile's credential env handles (see Configuration), then run a real statement against your account.cargo build --release -p franken-snowflake-cli --features live ./target/release/fsnow query run --profile demo-prod \ --sql "select current_version()" --json -
Write data. Enable writes for the profile, then run the mutation. Once
WRITE_ENABLEDis set, a barequery writeexecutes the statement directly and returns the live execution receipt.--dry-runstays available as an optional preview.export FRANKEN_SNOWFLAKE_DEMO_PROD_WRITE_ENABLED=true # Execute the mutation directly and return the live receipt. ./target/release/fsnow query write --profile demo-prod \ --sql "insert into events (id) select 1" --json # Optional: preview without executing (returns the plan plus a token). ./target/release/fsnow query write --profile demo-prod \ --sql "insert into events (id) select 1" --dry-run --json
The canonical binary is franken-snowflake; fsnow is the identical alias.
Read commands default to --json. Pass --toon for the token-efficient
encoding (available when the default toon feature is compiled in). --no-color
is accepted and ignored. There is no --version flag; the compiled version and
feature set are reported inside the capabilities and onboard envelopes.
Every command that takes --profile (or a positional <profile>) also reads
FRANKEN_SNOWFLAKE_DEFAULT_PROFILE: set that variable once and --profile
becomes optional, while an explicit value still wins. See
Configuration.
| Command | What it does |
|---|---|
fsnow onboard --json |
Mega-command: capabilities, exit codes, first commands, and health in one call |
fsnow capabilities --json |
The complete machine-readable command registry, including compiled feature_flags |
fsnow robot-docs guide |
An embedded agent guide for first-contact usage |
fsnow agent-handbook --json |
Envelope keys, exit codes, recovery commands, and non-goals |
fsnow doctor --json |
Local readiness checks |
fsnow selftest --json |
Deterministic protocol fixture readiness check |
fsnow help / fsnow --help / fsnow -h |
Top-level help envelope with did_you_mean on typos |
fsnow onboard --json
fsnow capabilities --toon
fsnow agent-handbook --json| Command | What it does |
|---|---|
fsnow profile validate <profile> --json |
Validate the profile id and the env-var handle names it references, with no live I/O |
fsnow profile doctor <profile> --json |
Inspect profile readiness offline |
fsnow profile doctor <profile> --online --json |
Attempt a minimal live probe (SELECT CURRENT_VERSION()); requires the live feature and credentials |
fsnow profile validate demo-prod --json
fsnow profile doctor demo-prod --json
fsnow profile doctor demo-prod --online --json # live feature + credentialsprofile validate and profile doctor (without --online) never read a secret
value and never touch the network; they report the env prefix and the expected
handle sets per auth lane.
| Command | What it does |
|---|---|
fsnow catalog scan <profile> --database <db> --schema <schema> --json |
Discover catalog metadata through INFORMATION_SCHEMA.TABLES |
fsnow catalog graph <profile> --database <db> [--schema <schema>] [--json|--toon|--mermaid|--svg] |
Render catalog lineage (profile to database to schema to object) |
Both --database and --schema are required for catalog scan. catalog graph requires --database and takes --schema optionally. Exactly one output
format may be chosen for catalog graph; mixing --mermaid with --json (or
two raw formats) is a usage error. Identifiers are validated as plain SQL
identifiers before interpolation, so a crafted value is rejected rather than
escaped-and-trusted. Live data requires the live feature plus credentials; the
default build returns a typed "live transport required" envelope.
fsnow catalog scan demo-prod --database ANALYTICS --schema PUBLIC --json
fsnow catalog graph demo-prod --database ANALYTICS --mermaid
fsnow catalog graph demo-prod --database ANALYTICS --schema PUBLIC --svg| Command | What it does |
|---|---|
fsnow dataset inspect <dataset-id> --json |
Return a dataset manifest with its column and operator catalogs |
fsnow dataset profile <dataset-id> --json |
Plan pushed-down APPROX_* column profiling for a dataset |
fsnow dataset describe-operator <operator> --jsonschema |
Return JSON Schema 2020-12 for a supported filter operator |
dataset describe-operator is fully offline and deterministic. dataset inspect and dataset profile return typed envelopes that describe the planned
shape while the dataset-manifest model is finalized.
fsnow dataset describe-operator between --jsonschema
fsnow dataset inspect events_daily --json
fsnow dataset profile events_daily --json| Command | What it does |
|---|---|
fsnow query plan --profile <profile> --sql <sql> --json |
Validate and explain a read plan without submitting it |
fsnow query run --profile <profile> --sql <sql> --json |
Submit a single read statement (SELECT / WITH / SHOW / DESCRIBE / EXPLAIN) |
fsnow query write --profile <profile> --sql <sql> [--dry-run | --confirm <token>] --json |
Execute a mutation; direct once WRITE_ENABLED is set, with --dry-run as an optional preview (see Writes) |
fsnow query --sql <sql> --profile <profile> --json |
Shorthand that maps to query run |
fsnow query cancel <statement-handle> --json |
Cancel a remote SQL API statement handle |
query plan runs offline: it validates the statement, refuses multiple
statements and mutating statements (UPDATE / DELETE / INSERT / MERGE / DDL), and
redacts secret-shaped SQL in its preview; to plan and execute a mutation, use
query write. query run is the read path: it accepts a single read statement,
applies the same safety check, then dispatches to the live transport when the
live feature is compiled and credentials are present; otherwise it refuses
cleanly with a typed envelope rather than substituting fixture or empty data. To
mutate data, use query write. Live read results are capped into the envelope
(with a truncated flag and a warning); full extraction uses a Snowflake-side
LIMIT or COPY INTO.
fsnow query plan --profile demo-prod --sql "select * from events limit 10" --json
fsnow query run --profile demo-prod --sql "select current_version()" --json
fsnow query cancel 01b2c3d4-0000-abcd-0000-000000000001 --jsonquery write executes INSERT, MERGE, UPDATE, DELETE, COPY INTO, and PUT against
the live account. Data writes are frictionless by default: once a profile sets
WRITE_ENABLED, a bare query write executes the statement and returns the live
execution receipt. write is a top-level alias for query write.
-
Enable writes for the profile. Data writes are off until you opt in per profile:
export FRANKEN_SNOWFLAKE_DEMO_PROD_WRITE_ENABLED=true -
Run the mutation. With the
livefeature and credentials present, the connector submits the statement directly and returns adata_source = "live"execution receipt with the statement handle and rows affected. No dry-run or confirmation token is required.fsnow query write --profile demo-prod \ --sql "insert into events (id) select 1" --json -
Preview first (optional).
--dry-runplans the statement and executes nothing (exit 0). The envelope reportsstatement_kind,safety_class, and arequired_confirmation_tokensuch asconfirm:insert:<idempotency-id>. The token is bound to (profile, SQL), so re-running with--confirm <token>executes only the same statement on the same profile.fsnow query write --profile demo-prod \ --sql "insert into events (id) select 1" --dry-run --json fsnow query write --profile demo-prod \ --sql "insert into events (id) select 1" \ --confirm confirm:insert:<id> --json
Cautious mode (opt-in). A profile can require the dry-run to confirm ceremony
on every write by setting WRITE_REQUIRE_CONFIRM=true. A bare query write then
refuses and tells you to --dry-run first, and execution requires the exact
--confirm <token>:
export FRANKEN_SNOWFLAKE_DEMO_PROD_WRITE_REQUIRE_CONFIRM=trueDDL (CREATE / ALTER / DROP / TRUNCATE / GRANT / REVOKE) needs a second opt-in on
top of WRITE_ENABLED; once set, DDL also executes directly (subject to
WRITE_REQUIRE_CONFIRM like everything else):
export FRANKEN_SNOWFLAKE_DEMO_PROD_WRITE_ALLOW_DDL=trueTyped refusals keep the write path honest:
| Code | Meaning |
|---|---|
FSNOW-3007 |
Writes are not enabled for the profile; set <PREFIX>_WRITE_ENABLED=true |
FSNOW-3008 |
This profile sets WRITE_REQUIRE_CONFIRM=true and no matching confirmation token was supplied; run --dry-run, then --confirm <token> |
FSNOW-3009 |
The statement is DDL and DDL is not opted in; set <PREFIX>_WRITE_ALLOW_DDL=true |
FSNOW-2003 |
A required credential handle is missing |
Without the live feature or without credentials, query write refuses cleanly
with a typed envelope (the write is authorized, but the execution rung reports
that live transport and credentials are required); it never fakes an execution.
| Command | What it does |
|---|---|
fsnow receipt show <receipt-hash> --json |
Look up a content-addressed query receipt |
fsnow export plan --json |
Draft a COPY INTO or local CSV/JSONL export plan; run a COPY INTO through query write |
fsnow export plan --json
fsnow receipt show 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 --json| Command | What it does |
|---|---|
fsnow mcp serve --stdio |
Serve the read verbs as MCP tools over stdio (requires the mcp feature) |
fsnow mcp serve --http <addr> |
Serve over HTTP at the given address |
fsnow tui --profile <profile> |
Launch the interactive TUI (requires the tui feature; opt-in and default-off) |
fsnow mcp serve --stdio
fsnow mcp serve --http 127.0.0.1:3000
fsnow tui --profile demo-prod--stdio and --http are mutually exclusive. See the
MCP surface section for the tool roster.
The CLI does not currently expose a completions subcommand. Agents and
installer scripts should discover commands and flags through fsnow capabilities --json rather than a generated completion file.
A profile is a stable, lowercase-ish handle (1 to 128 ASCII letters, digits, dot, dash, or underscore). Profiles carry no secrets. Instead, each profile maps to a set of environment-variable handles, and the live transport reads those at request time.
A profile name is uppercased and its dots, dashes, and underscores are
normalized to _, then prefixed with FRANKEN_SNOWFLAKE_. The profile
demo-prod therefore uses the prefix FRANKEN_SNOWFLAKE_DEMO_PROD.
| Handle | Purpose |
|---|---|
<PREFIX>_ACCOUNT |
Snowflake account locator or full https://...snowflakecomputing.com URL |
<PREFIX>_USER |
Snowflake user |
<PREFIX>_AUTH |
Auth lane: pat, oauth_bearer, or key_pair_jwt |
<PREFIX>_WAREHOUSE |
Warehouse for submitted statements |
<PREFIX>_DATABASE |
Optional default database (overridden by --database) |
<PREFIX>_SCHEMA |
Optional default schema (overridden by --schema) |
<PREFIX>_ROLE |
Optional role |
<PREFIX>_MAX_POLLS |
Optional poll budget (default 120) |
<PREFIX>_WRITE_ENABLED |
Set to true to enable data writes (DML, COPY INTO, PUT) for the profile; a bare query write then executes directly |
<PREFIX>_WRITE_REQUIRE_CONFIRM |
Set to true to require the dry-run to confirm ceremony on every write (cautious opt-in); a bare query write refuses until you --dry-run, then --confirm <token> |
<PREFIX>_WRITE_ALLOW_DDL |
Set to true to additionally allow DDL (CREATE/ALTER/DROP/TRUNCATE/GRANT/REVOKE) through query write |
These apply across profiles rather than to a single profile prefix.
| Variable | Purpose |
|---|---|
FRANKEN_SNOWFLAKE_DEFAULT_PROFILE |
Default profile used when --profile (or the positional <profile>) is omitted. Set it once to make --profile optional on every command; an explicit profile always wins. |
# Make --profile optional for the rest of the session.
export FRANKEN_SNOWFLAKE_DEFAULT_PROFILE=demo-prod
fsnow query plan --sql "select 1" --json # resolves to demo-prodThe secret value is referenced by env-var name and resolved at request time; it is never stored in config and never read into a diagnostic message.
Auth lane (<PREFIX>_AUTH) |
Secret handle(s) |
|---|---|
pat (programmatic access token) |
<PREFIX>_PAT |
oauth_bearer |
<PREFIX>_OAUTH_BEARER |
key_pair_jwt |
<PREFIX>_PRIVATE_KEY_PEM, optional <PREFIX>_PRIVATE_KEY_PASSPHRASE, optional <PREFIX>_JWT_VALIDITY_SECONDS |
Auth lanes are implemented in this order: programmatic access token (PAT) for fast administrator-managed onboarding, key-pair JWT for long-lived service users and rotation, OAuth bearer where an OAuth flow already exists, and workload identity federation only after the first three are stable.
export FRANKEN_SNOWFLAKE_DEMO_PROD_ACCOUNT="xy12345.us-east-1"
export FRANKEN_SNOWFLAKE_DEMO_PROD_USER="SVC_AGENT"
export FRANKEN_SNOWFLAKE_DEMO_PROD_AUTH="pat"
export FRANKEN_SNOWFLAKE_DEMO_PROD_WAREHOUSE="COMPUTE_WH"
export FRANKEN_SNOWFLAKE_DEMO_PROD_PAT="..." # resolved at request time, never logged
# Allow data writes (DML/COPY INTO/PUT); a bare `query write` then executes directly:
export FRANKEN_SNOWFLAKE_DEMO_PROD_WRITE_ENABLED=true
# Confirm the handles are present (no network, no secret read):
fsnow profile validate demo-prod --json
# With a live build, probe connectivity without emitting any secret:
fsnow profile doctor demo-prod --online --jsonThe default build links no live transport. Discovery, self-description, profile
validation, offline query plan, dataset describe-operator, and query write
previews (--dry-run, once a profile sets WRITE_ENABLED) all work with no
credentials. The live data-plane verbs (catalog scan, catalog graph live
source, query run, query write execution, profile doctor --online) require
the live feature at build time and the profile's credential handles at run
time. When the feature is present but a handle is missing, the command returns a
typed credential error (exit 3); it never silently returns empty or fixture
data. On success, the envelope carries data_source = "live" and the real
statement handle.
agent or human
|
v
franken-snowflake / fsnow CLI == mcp serve (shared handlers, one contract)
|
offline agent surface (no credentials needed)
onboard · capabilities · robot-docs · agent-handbook · doctor · selftest
profile validate · profile doctor · dataset describe-operator
query plan · query write (dry-run) · catalog graph render · export plan draft
|
v
franken-snowflake-core
envelope · capabilities · outcome/exit · error registry · ids
guardrails (cost/safety) · budget · cancel · redact · write_intent · adapter
|
v
live transport (feature = "live", credential-gated at runtime)
reads: query run · catalog scan · profile doctor --online
writes: query write --confirm (INSERT/MERGE/UPDATE/DELETE/COPY INTO/PUT/DDL)
|
v
auth (PAT · key-pair JWT RS256 · OAuth bearer) redaction policy + leak gate
http (Asupersync HTTP/1.1 + TLS + gzip, retries, Retry-After, submit-retry guard)
sqlapi (submit · poll · partition stream · cancel · jsonv2 wire codec)
|
v
catalog (info-schema discovery · model · operator · planner · predicate AST)
graph (lineage · Mermaid/SVG) · frame (fp-columnar/fp-types) · export (COPY INTO + CSV/JSONL)
cache (FrankenSQLite/sqlmodel metadata store) · text-indexing (frankensearch hash/lexical)
|
v
Snowflake SQL API (HTTPS)
testkit (parallel to all of the above; no warehouse required)
deterministic codec lane under the lab runtime · mock SQL API server
replay · DPOR cancel/retry race suite · golden/clock/canary/logger harness
A submitted statement is modeled as an Asupersync bracket, so cancellation
always reaches Snowflake's remote cancel endpoint and no statement is orphaned.
A query write runs in two rungs: a dry-run plans and emits a confirmation
token, and a confirm submits the authorized mutation through the same live
transport as a read. The dataset planner compiles a named dataset plus entity
and date-range hints into pushed-down SQL with positional typed bindings; raw
SQL mode is the expert path. Both modes share one planner.
With the mcp feature compiled in, fsnow mcp serve exposes the connector's
read verbs as MCP tools backed by the same CLI handlers and the same JSON
envelope, so the CLI and the MCP server cannot diverge into two contracts. The
server runs over stdio or HTTP and is stdio-first by design; data writes go
through the CLI query write ladder.
The exposed tools mirror the CLI read and discovery verbs:
capabilities onboard doctor
agent_handbook robot_docs_guide selftest
profile_validate profile_doctor catalog_scan
catalog_graph dataset_inspect dataset_profile
dataset_describe_operator query_plan
query_run query_cancel receipt_show
export_plan
# Build with MCP, then serve over stdio for a local agent.
cargo build --release -p franken-snowflake-cli --features mcp
fsnow mcp serve --stdio| Symptom | Likely cause | Fix |
|---|---|---|
Unknown flag or Unknown command (exit 64) |
Typo in a flag or verb | The envelope's did_you_mean lists the closest matches; run fsnow capabilities --json for the full registry |
query run returns a "live transport required" envelope |
The binary was built without the live feature |
Rebuild with --features live, then export the profile's credential handles |
| Credential error (exit 3) on a live command | A required <PREFIX>_* handle is missing |
Run fsnow profile validate <profile> --json to see the expected handle set, then export the missing ones |
--toon rejected |
The toon feature is not compiled in |
Use --json, or rebuild with the default features (which include toon) |
Safety refusal (exit 2) on query run / query plan |
The SQL is a mutation, DDL, or multiple statements | query run and query plan take a single read statement (SELECT / WITH / SHOW / DESCRIBE / EXPLAIN); to change data, use query write |
query write refuses with FSNOW-3007 |
Writes are not enabled for the profile | export FRANKEN_SNOWFLAKE_<PROFILE>_WRITE_ENABLED=true, then run query write directly |
query write refuses with FSNOW-3008 |
The profile sets WRITE_REQUIRE_CONFIRM=true and no matching token was supplied |
Run query write --dry-run to get the token, then re-run with --confirm <token> (or unset the handle for direct writes) |
query write refuses with FSNOW-3009 |
The statement is DDL and DDL is not opted in | export FRANKEN_SNOWFLAKE_<PROFILE>_WRITE_ALLOW_DDL=true |
query write returns a "live transport required" envelope |
The binary was built without the live feature |
Rebuild with --features live, then export the profile's credential handles |
mcp serve reports the feature is unavailable |
The binary was built without the mcp feature |
Rebuild with --features mcp |
Exit codes are stable and coarse: 0 success (including empty results), 1
findings or warnings, 2 safety refusal, 3 credential or profile error, 4
upstream Snowflake error, 5 network error or retry budget exhausted, 6 query
still running, 7 local cache error, 64 usage error, 74 I/O error. Each
error also carries a stable FSNOW-<code> string (for example FSNOW-2003
credential missing, FSNOW-3001 mutation refused on the read path, FSNOW-3002
multi-statement refused, FSNOW-3007 writes not enabled, FSNOW-3008
confirmation required, FSNOW-3009 DDL not opted in) with an exact next command.
- Live transport is a build feature. It compiles behind the
livefeature; build with--features livefor real Snowflake access. Even then it is gated at runtime by credential availability and never substitutes fixture or empty data. - Distribution is binary-first; source builds resolve from crates.io. The
public installers download prepared GitHub release archives by default. A
--from-sourcebuild works from a fresh standalone clone: the FrankenSuite dependencies (asupersync, frankensqlite, fastmcp_rust, sqlmodel, and others) resolve from crates.io, so no local sibling checkout is required. query runaccepts exactly one read statement, andquery writeaccepts exactly one mutating statement; multiple-statement requests are refused.- Data writes (DML, COPY INTO, PUT) execute directly once a profile sets
WRITE_ENABLED; DDL needs the additionalWRITE_ALLOW_DDLopt-in, andWRITE_REQUIRE_CONFIRMre-arms the dry-run to confirm ceremony for cautious profiles. dataset inspectanddataset profilecurrently return typed "planned" or "requires model" envelopes while the dataset-manifest model is finalized.- Local Arrow/Parquet export is not implemented yet; local export covers
CSV/JSONL, and large export uses Snowflake-side
COPY INTO. - The TUI ships behind the
tuifeature (opt-in, default-off). - The CLI has no
completionssubcommand; discover commands viacapabilities. - The whole stack requires a nightly Rust toolchain (edition 2024), inherited from the FrankenSQLite, sqlmodel, and Asupersync dependency set.
Is this usable today? Yes. Built with --features live and a profile's
credential handles, it runs real queries against live Snowflake accounts and
loads data through query write. The default credential-free build covers
offline contract work, deterministic fixtures, and CI.
How do I load or write data? Enable writes for the profile with export FRANKEN_SNOWFLAKE_<PROFILE>_WRITE_ENABLED=true, then run query write: with the
live feature and credentials present, a bare write executes the statement
directly and returns a data_source = "live" receipt with the statement handle
and rows affected. --dry-run is an optional preview that returns a confirmation
token bound to (profile, SQL), and --confirm <token> then executes that exact
statement. Set FRANKEN_SNOWFLAKE_<PROFILE>_WRITE_REQUIRE_CONFIRM=true to require
that ceremony on every write. DDL additionally needs export FRANKEN_SNOWFLAKE_<PROFILE>_WRITE_ALLOW_DDL=true. INSERT, MERGE, UPDATE, DELETE,
COPY INTO, and PUT all run through this path.
Why not just use an official driver? Snowflake publishes none for Rust, and the goal here is a Rust-first, Tokio-free, agent-ergonomic client, a niche the official drivers do not cover. If you are not in Rust and need production today, use an official driver.
Why not a third-party Rust Snowflake crate? Those may be studied as read-only inspiration, but the policy forbids vendoring them or adding them as production dependencies; the dependency graph and the clean-room posture matter here.
Why Asupersync instead of Tokio? Cancellation correctness, capability security, structured budgets, and deterministic lab and DPOR tests come from one audited runtime. Production crates forbid Tokio, reqwest, hyper, axum, and tower.
Can I develop and test offline? Yes. The deterministic testkit (a codec lane plus a mock SQL API server) exercises the protocol with no warehouse and no credentials. Live tests are opt-in and refuse clearly when credentials are absent.
How do I turn on live Snowflake access? Rebuild with --features live,
define the profile's FRANKEN_SNOWFLAKE_<PROFILE>_* env handles, and run query run for reads or query write for writes. Without the feature or the handles,
the command refuses cleanly instead of guessing.
Can an agent call this as tools instead of shelling out? Yes. Build with
--features mcp and run fsnow mcp serve --stdio. Every read verb becomes an
MCP tool with the same envelope contract as the CLI; data writes go through the
CLI query write ladder.
Where are the issues tracked? In Beads
(br), synced to JSONL in this repository, not GitHub issues. Use
br ready --json for actionable work and br dep cycles for graph health.
Please don't take this the wrong way, but I do not accept outside contributions
for any of my projects. I simply don't have the mental bandwidth to review
anything, and it's my name on the thing, so I'm responsible for any problems it
causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also
have to worry about other "stakeholders," which seems unwise for tools I mostly
make for myself for free. Feel free to submit issues, and even PRs if you want to
illustrate a proposed fix, but know I won't merge them directly. Instead, I'll
have Claude or Codex review submissions via gh and independently decide whether
and how to address them. Bug reports in particular are welcome. Sorry if this
offends, but I want to avoid wasted time and hurt feelings. I understand this
isn't in sync with the prevailing open-source ethos that seeks community
contributions, but it's the only way I can move at this velocity and keep my
sanity.
MIT License (with OpenAI/Anthropic Rider). See LICENSE.
