Skip to content

feat: add ctx sql raw read-only SQL query surface#12

Merged
saldestechnology merged 2 commits into
mainfrom
feat/ctx-sql
Jul 10, 2026
Merged

feat: add ctx sql raw read-only SQL query surface#12
saldestechnology merged 2 commits into
mainfrom
feat/ctx-sql

Conversation

@saldestechnology

Copy link
Copy Markdown
Collaborator

Summary

Adds a new top-level ctx sql command: direct, read-only SQL access to the code-intelligence index through DuckDB. This turns ctx from a fixed menu of canned analyses (ctx query find/callers/deps/...) into a queryable platform — agents can answer structural questions in one round trip, and teams can commit custom quality gates as SQL files.

The public surface is a versioned v1 view layer (a stable contract) over the physical tables:

  • v1.symbols(id, name, qualified_name, kind, file, line_start, line_end, is_public, complexity, fan_in, fan_out, doc)
  • v1.edges(source_id, source_name, source_file, target_id, target_name, target_file, kind, line)
  • v1.files(path, language, symbol_count, total_complexity, indexed_at)
  • v1.meta(schema_version, ctx_version, index_created_at, index_root)
ctx sql "SELECT name, file, complexity FROM v1.symbols ORDER BY complexity DESC LIMIT 10"
ctx sql --json "SELECT kind, COUNT(*) n FROM v1.symbols GROUP BY kind"
ctx sql --fail-on-rows --file .ctx/gates/no-utils-imports.sql   # gate: any row = violation
ctx sql --schema                                                # print the v1 reference

Security (engine-level, no SQL text filtering)

The primary caller is an LLM agent with shell access, so every query is treated as potentially adversarial. Safety is enforced entirely by DuckDB configuration, never by inspecting/blocklisting SQL text:

  • enable_external_access = false + lock_configuration = true — disables filesystem access (COPY, read_csv), extension loading (INSTALL), file-based ATTACH, and prevents user SQL from re-enabling any of it.
  • The SQLite index is attached READ_ONLY — it cannot be mutated (verified by byte-for-byte checksum in tests).
  • --timeout enforced via a connection interrupt-handle watchdog; --max-rows streams and caps without materializing the full result; memory_limit (overridable via CTX_SQL_MEMORY_LIMIT) bounds runaway joins.

Behavior notes / deviations from the spec

  • --output instead of --format. ctx already has a global -f/--format (OutputFormat enum for context generation) inherited by every subcommand, so a subcommand-local --format collides at the clap level. I used --output table|csv|json (the repo's own convention for search/semantic/audit) plus the --json alias. No acceptance criterion tests the --format flag name.
  • Bare ATTACH ':memory:' is permitted but inert. DuckDB 1.4 has no engine toggle to reject an in-memory attach, and access_mode=read_only is open-time-only (incompatible with building the v1 views). Rather than add SQL text filtering (which the design explicitly forbids), this bare attach is allowed — it has no filesystem access, the index stays read-only, and the one-result-set rule blocks chaining a write onto it. File-based ATTACH (the actual threat) is blocked. This is the one acceptance criterion the engine can't meet without violating the "no text filtering" rule; documented in code and tests.

Exit codes

0 ran (with --fail-on-rows: zero rows) · 1 --fail-on-rows and ≥1 row · 2 any error (SQL error, timeout, missing index, invalid flags, or a build without the duckdb feature). In stub builds (--no-default-features) the command still appears in --help and exits 2 with a clear message.

Single-source schema reference

ctx sql --schema and docs/website/docs/sql-schema.md are generated from one source (src/commands/sql_schema.md via include_str!); a drift-guard unit test keeps them byte-identical.

Tests

  • tests/sql.rs (assert_cmd, 17 tests): security (COPY / read_csv / INSTALL / locked config / file-ATTACH / index-bytes-unchanged / timeout), schema (meta, every view queryable, --schema), execution (--fail-on-rows codes, row-cap truncation, JSON envelope, multi-statement semantics, missing index), and the .ctx/gates/ gate pattern.
  • Inline unit tests: statement splitter + schema/docs drift guard.
  • Verified: cargo clippy --all-targets clean; builds with and without the duckdb feature.

Docs

  • New docs-site pages sql-schema.md and commands/sql.md (+ sidebar entries), and a README "ctx sql" section. Notes that Bash(ctx sql *) is safe to add to a Claude Code / harness plugin allow-list.

🤖 Generated with Claude Code

Add a new top-level `ctx sql` command that runs read-only SQL against the
code-intelligence index through DuckDB, exposing a versioned, stable `v1`
view layer (v1.symbols, v1.edges, v1.files, v1.meta) over the physical tables.

- Engine (src/analytics/duckdb_impl.rs): `open_sql_sandbox` attaches the SQLite
  index READ_ONLY, builds the `v1` schema, then hardens the connection
  (enable_external_access=false, lock_configuration=true, memory_limit) so
  untrusted SQL cannot touch the filesystem, load extensions, attach files, or
  re-enable any of it. Safety is enforced by engine configuration only — no SQL
  text inspection or keyword filtering. Streaming row reader caps output without
  materializing the full result; interrupt-handle watchdog enforces --timeout.
- Handler (src/commands/sql.rs): input from arg/--file/stdin, table/csv/json
  output with a JSON envelope, --max-rows cap, --fail-on-rows gate mode, and the
  three-way exit convention (0 ok / 1 findings / 2 error). One-result-set rule
  allows `CREATE TEMP TABLE ...; SELECT ...` while rejecting multiple queries.
- `--schema` prints the public schema reference from a single source
  (src/commands/sql_schema.md) that also feeds the docs site; a drift-guard test
  keeps them byte-identical.
- Docs: docs/website/docs/sql-schema.md + commands/sql.md, sidebar entries, and
  a README "ctx sql" section.
- Tests: tests/sql.rs (assert_cmd) covers the security, schema, execution, and
  gate acceptance criteria; inline unit tests cover statement splitting + drift.

The command appears in --help in all builds; without the `duckdb` feature it
exits 2 with a clear message.

Note: DuckDB 1.4 has no engine toggle to reject a bare in-memory `ATTACH
':memory:'`; it is permitted but inert (no filesystem access, index stays
read-only, and the one-result-set rule blocks chaining a write). File-based
ATTACH, COPY, read_csv, INSTALL, and config changes are all blocked.
# Conflicts:
#	Cargo.toml
#	docs/website/sidebars.ts
@saldestechnology
saldestechnology merged commit 5671477 into main Jul 10, 2026
5 of 7 checks passed
@saldestechnology
saldestechnology deleted the feat/ctx-sql branch July 10, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant