franken_snowflake
Health Warn
- License — License: NOASSERTION
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Clean-room, Rust-first, Asupersync-native Snowflake SQL API connector built for coding agents. 14-crate workspace, live reads and writes, no ODBC or JDBC.
franken_snowflake
A clean-room, Rust-first Snowflake SQL API connector built for coding agents.
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 sidequery 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.
TL;DR
The Problem
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.
The Solution
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.
Why franken_snowflake?
| 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 |
Quick Example
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 --mermaid
With 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 --json
fsnow 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.
Design Philosophy
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, anddbg_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.
How It Compares
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.
Installation
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 (Linux and macOS)
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/franken_snowflake/main/install.sh | bash
PowerShell (Windows)
irm https://raw.githubusercontent.com/Dicklesworthstone/franken_snowflake/main/install.ps1 | iex
The 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 -Live
From source
git 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-cli
The 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,live
Both 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.
Quick Start
Build the CLI.
cargo build --release -p franken-snowflake-cliOrient yourself in one call.
./target/release/fsnow onboard --jsonCheck local readiness (no network).
./target/release/fsnow doctor --jsonPlan 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" --jsonRun 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()" --jsonWrite 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
Command Reference
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 readsFRANKEN_SNOWFLAKE_DEFAULT_PROFILE: set that variable once and --profile
becomes optional, while an explicit value still wins. See
Configuration.
Discovery and self-description
| 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
Profiles
| 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 + credentials
profile 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.
Catalog discovery
| 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
Datasets
| 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
Queries
| 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, usequery 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 thelive 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-sideLIMIT 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 --json
Writes
query write executes INSERT, MERGE, UPDATE, DELETE, COPY INTO, and PUT against
the live account. Data writes are frictionless by default: once a profile setsWRITE_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=trueRun 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" --jsonPreview 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=true
DDL (CREATE / ALTER / DROP / TRUNCATE / GRANT / REVOKE) needs a second opt-in on
top of WRITE_ENABLED; once set, DDL also executes directly (subject toWRITE_REQUIRE_CONFIRM like everything else):
export FRANKEN_SNOWFLAKE_DEMO_PROD_WRITE_ALLOW_DDL=true
Typed 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.
Receipts and export
| 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
MCP and TUI
| 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.
Note on shell completions
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.
Configuration
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.
Env-var naming
A profile name is uppercased and its dots, dashes, and underscores are
normalized to _, then prefixed with FRANKEN_SNOWFLAKE_. The profiledemo-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 |
Global environment variables
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-prod
Secret handles by auth lane
The 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.
Example: a live PAT profile with writes enabled
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 --json
Offline contracts and live data
The 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.
Architecture
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.
MCP surface
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
Troubleshooting
| 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.
Limitations
- 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-sideCOPY 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.
FAQ
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 thelive 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. Usebr ready --json for actionable work and br dep cycles for graph health.
About Contributions
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.
License
MIT License (with OpenAI/Anthropic Rider). See LICENSE.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found