Skip to content

sutura

sutura is being built to answer questions about data as the person or agent asking, using metric definitions somebody certified, and to refuse when it cannot do either.

Give an agent a database connection and it answers with SQL it invented, run under whatever credential the service holds. Two failures, not one. The number is uncertified, so nobody can say whether "revenue" means what finance means by it. The rows are the ones the service may read rather than the ones the caller may read, which is how a row-level security policy becomes decorative.

What is built, and what is a design target

sutura today is a governed single-player semantic compiler and executor over local files. The half of the design that fixes the second failure above is half built, and the missing half is the one that matters. A request context, a credential broker port, an audit sink and an MCP surface all exist, and a deployment that declares security.inbound verifies a caller's own token - so who is asking can be known, no question can execute without a credential minted for the source it reads, and every outcome is recorded. What is absent is a data system that evaluates the asking subject: no adapter in this build can carry a per-subject credential, so every question still reads as one identity. No Arrow result envelope either. Every claim on this site is marked enforced today or design target at the point it is made.

The four properties

Three columns, deliberately. The middle one is the design; the last one says whether anything holds it in the code that is here now.

Property The mechanism it rests on Status
Every query runs as the caller A credential minted per request for the calling principal. A leg that cannot run as the subject is refused, never downgraded to a service identity Design target, not built. No caller identity reaches the query path as the subject, and in single-player the property is trivially true and worth nothing: a file has no login, so there is nobody else to be
A refusal is an answer Refusal is a variant of the result type rather than an error return, so a caller cannot mistake it for a hiccup and retry until something works Enforced today. ToolOutcome::Refusal is the public surface and the golden suite provokes every reachable variant. Recording it against the principal chain is enforced too - every outcome goes through an AuditSink before it is returned - with two limits: sutura retains nothing, and behind the shared bearer token alone the subject recorded is the deployment
You cannot ask it to run SQL The tool surface has no field for a query, a table or a filter. An uncertified question is unrepresentable, not merely refused Enforced today. Query declares no such field, deny_unknown_fields turns an attempt into an error naming it, and a golden asserts no value a question carries reaches the statement as text
Definitions come from elsewhere They are authored in a semantic layer and arrive pinned and hashed. Nothing here edits one, because that would fork the definition from the number it certifies Enforced today. The load path takes no request context, the bundle is hashed, and every declared anchor re-executes before the bundle may be served

Those mechanisms are the design. Architecture says how the four force the shape of the system and which parts are compiled today. AGENTS.md in the repository lists every invariant beside the type, lint or gate that enforces it - including the rows that say outright that nothing enforces them yet.

What it borrows

Two Apache-2.0 projects got there first.

  • Wren compiles a modelled question into SQL over DataFusion. The compile-a-plan-from-a-model shape comes from there.
  • Spice, also DataFusion-based, federates and accelerates queries across sources.

Between them they cover compiling a question and federating it. What sutura means to add is identity: not only what a question means, but who is asking and whether they may see the answer. That is the designed and unbuilt part, so what differs today is the narrow tool surface and the pinned bundle, not the identity.

Where the parts come from sets those two and polyglot on the line from a modelled question to executed SQL, and says which parts we mean to build.

Status

The query path is built, and it supports exactly one combination: metadata from a catalogue of markdown documents with YAML frontmatter in git, executed by the in-process engine over the CSV or Parquet files you point it at. A question naming a metric, a grain, a bounded range and some dimensions compiles to a plan and executes, and every metric that declares a certified number reproduces it before the bundle can be served.

The plan can also be rendered as SQL for DuckDB, Postgres or ClickHouse, and there is a DuckDB adapter that pushes a statement down - but that adapter is a test dependency, not the runtime data source, and the shipped binary links neither it nor any driver. Which DATA SYSTEM is opened is a declaration - sources.<alias>.kind, the same tree the service reads - and which KINDS a given build can open is a compile-time decision: a published binary opens files, and kind: bigquery needs a build carrying the default-off feature, which is a refusal naming that feature rather than a silence. What can be plugged in today is the table, and it is the section to read before assuming otherwise.

What is not built is the part that makes the first sentence of this page true of a warehouse, and it is now one specific thing rather than four. There is a request context, a credential broker port with a static-credential implementor, an audit sink, an MCP surface, and - where a deployment declares security.inbound - a caller identity verified from a signature, with scopes deciding which operations that caller may invoke. What is absent is leg 2: no adapter in this build has anywhere for a per-subject credential to arrive, both declare so, and the broker mints what an operator configured. So "as the person or agent asking" holds here only because a file has nobody else to be - a deployment can know exactly who is asking, record it, refuse a subject it holds no credential for, and still read every row as one identity. Arrow results are also still ahead. Federation is not: the splitter, two executions and the combiner all run in a published build, because the engine declares Warehouse::EXECUTES_LEGS. The HTTP surface is not, and its bearer token authenticates the deployment rather than the caller. What exists today is the honest inventory.

Where to start

You want to Read
Install it and ask a question Getting started
Know what the words on the tool surface mean Concepts
Ask the short questions first Questions and answers
Understand the shape of the system Architecture
Read the Rust API API reference

To work on sutura, start at Contributing under Development.

Feedback and reporting

For questions, feature ideas and bug reports, open an issue in the issue tracker. For a suspected vulnerability, report it privately rather than in a public issue - see SECURITY.md.