Misata: synthetic data library

io.github.rasinmuhammedPublished by GitHub account rasinmuhammedv0.9.8Updated Nov 2, 2026

Declarative synthetic data: multi-table data that holds the facts you declare, checked by DuckDB.

The publisher's own server: Misata Studio: verified synthetic data

VerifiedSTDIODesktop onlyDatabasesData & AnalyticsAI & ML

Overview

AI-generated overview

Misata generates declarative synthetic multi-table datasets that satisfy declared facts, then verifies them with DuckDB and a re-runnable certificate.

What you can do
  • Generate datasets from a YAML or dict schema, a plain sentence, SQL DDL, or a story domain, with feasibility checks before any row exists
  • Explain, validate and inspect schemas, preview story domains, and list available domains
  • Seed a live database, create and query a sandbox, and solve a pipeline toward a declared outcome
  • Attest and audit generated datasets and check how realistic they look
  • Produce CSV, Parquet, JSONL, DuckDB or Postgres output plus a verifier script that re-checks every derived invariant
When to use it
Useful when you need test fixtures, dev or staging databases, eval data, agent sandboxes or demo tenants whose expected answers are known independently of the code under test, and you want an assistant to design the schema while the engine does the arithmetic and DuckDB certifies it.
Requirements
Runs as a local stdio process, installed from PyPI with uvx (misata with the mcp extra). Python and DuckDB are needed for generation and verification; seeding a live database requires a connection string for that database.
Before you install
Seeding a database writes rows into it, and truncation only happens when explicitly requested. Publishing uploads data to a third party. The mimic feature fits to real rows and is explicitly not anonymous, so do not treat it as a privacy tool. Generated priors are English-first and US-leaning.

Installation

In SourceWeft

  1. Open Misata: synthetic data library in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

[Misata]

Misata

Declare what must be true. Get data that makes it true. Prove it.

Misata is a declarative synthetic data generator for Python. You write down the facts a dataset has to satisfy, from a monthly revenue curve to a balanced ledger to a foreign key that must never dangle, and the engine builds multi-table data that satisfies all of them at once. Then DuckDB checks the files on disk and writes a certificate anyone can re-run.

[PyPI] [Python] [Tests] [License] [Open in Colab] [smithery badge]

Docs · Use cases · The language · Limitations · Studio · Changelog


Sixty seconds

bash
pip install "misata[duckdb]"
yaml
# misata.yamlseed: 7tables:  customers:    rows: 2000    columns:      customer_id: {type: int, unique: true, min: 1, max: 2000}      name: {type: text, text_type: name}      signed_up: {type: date, start: "2024-01-01", end: "2024-12-31"}  orders:    rows: 40000    columns:      order_id: {type: int, unique: true, min: 1, max: 1000000}      customer_id: {type: foreign_key}      channel: {type: categorical, choices: [web, app, store]}      amount: {type: float, min: 5, max: 900}      ordered_at: {type: date, start: "2025-01-01", end: "2025-12-31"}relationships:  - customers.customer_id -> orders.customer_idoutcome_curves:  - table: orders    column: amount    time_column: ordered_at    start_date: "2025-01-01"    curve_points:      - {month: 1, target_value: 180000}      - {month: 6, target_value: 240000}      - {month: 12, target_value: 310000}group_shares:  - table: orders    measure: amount    group_column: channel    shares: {web: 0.5, app: 0.3, store: 0.2}
bash
misata generate -c misata.yaml -o data --attestpython data/verify.py
text
Attestation: data  result: PASSED  files pinned: 2  invariants held: 69 of 69
ok    2 file(s) match their attested sha256ok    69 of 69 invariant(s) hold

The 69 are derived from the YAML before a row exists: twelve monthly revenue totals (180,000.00 in January, 240,000.00 in June, 310,000.00 in December, straight lines between), the channel split inside every one of those months, the planned row count per month, zero orphaned customer_ids and unique keys. verify.py needs only the standard library and duckdb, and it shares no code with the generator.

Change one cent in one row and it says so:

text
FAIL  orders.csv: sha256 301165f9dc4e does not match the attested 670f89195826FAIL  orders.amount.month:1.outcome_curve_period: expected 180000.00, observed 180000.01FAIL  orders.amount.outcome_curve_total: expected 2945000.00, observed 2945000.01FAIL  orders.amount.web.2025-01.group_share_period: expected 90000.00, observed 90000.01FAIL  orders.amount.web.group_share_total: expected 1472500.00, observed 1472500.01FAIL  65 of 69 invariant(s) hold

Why declare instead of generate

Every test fixture has an oracle problem. To check a dbt model, a Spark job, a dashboard tile or a data agent you need input data and an answer you know independently of the code under test. Copying production gives you data without an answer. Hand-written fixtures give you an answer that rots. Random generators give you data whose answer you have to compute with the code you are testing.

Misata turns it around. The specification is the ground truth and the rows are derived from it. "January revenue is 180,000" is true before a single row exists, so your pipeline either returns 180,000.00 or it has a bug.

mermaid
flowchart LR    A["Declare<br/>curves, shares, ledgers,<br/>keys, lifecycles"] --> B{"Can they<br/>all hold?"}    B -- "no" --> R["Refused, with the<br/>arithmetic and a fix"]    B -- "yes" --> C["Build rows<br/>closed form where it can,<br/>by construction elsewhere"]    C --> D["Write<br/>CSV, Parquet, JSONL,<br/>DuckDB, Postgres"]    D --> E["Attest<br/>DuckDB re-runs every<br/>derived invariant"]    E --> F["Certificate<br/>in-toto Statement<br/>plus verify.py"]

Three properties make this different from a generator with options:

  • A declaration is honoured or refused, never quietly replaced. Declarations that cannot all hold stop generation with an error that names each one, shows the arithmetic and suggests the change.
  • The certificate is checked by code that did not write the data. A bug in Misata's arithmetic shows up as a failed invariant, not a self-consistent wrong answer.
  • The same spec, seed and version give the same bytes on Linux, macOS and Windows, so a fixture can live in git with its fingerprint.

Where this is going

Misata's aim is that nobody writes a Python script to make a dataset, of any kind. You say what has to be true; the engine works out the rows, refuses what cannot hold, and proves what it built.

It is not there yet, and it is getting there brick by brick. Each release adds things you can declare instead of programming, and a brick is laid only when it passes three tests: it can be declared in a few lines of YAML, it is refused before generation when it contradicts another declaration, and it is re-checked on the rows afterwards by code that did not write them.


One fact, two ways. What a script has to do to make a fact true, and what you write instead:

The factIn a generator scriptIn Misata
January revenue is 180,000.00draw amounts, sum them, rescale, round, chase the last cent{month: 1, target_value: 180000}
web is half of every month's revenuerescale inside each month and channel, then repair the monthly totals you just brokeshares: {web: 0.5, app: 0.3, store: 0.2}
every order has a real customersample ids from the parent table and keep both in stepcustomers.customer_id -> orders.customer_id
some churned customers were active firstbranch on status, write timestamps in order, null the rest, count by handtransitions: [[trialing, active], [active, churned]]
1% of transfers form laundering patterns, in a set mixbuild each ring and fan by hand, then prove the background has no cycle of its ownrate: 0.01 and shares: {cycle: 0.4, fan_in: 0.25, ...}
the trial balance is zeropost both legs of every entry and reconcile at the enda balanced_ledger constraint on the journal lines

What still takes code, and where each one stands:

Still scripted by handWhere it stands
The expected output of logic Misata cannot invert: window functions, custom SQL, business rulesmisata solve drives your real dbt, SQL or Python pipeline to a declared number within a stated tolerance; past that, you write the expected value
Correlations you can only describe by examplemisata.mimic fits them from real rows, and its output is not anonymous
Free text with a voice of its owntext is generated from grammars and agrees with its row; prose that reads as written is not declarable yet
Version histories (SCD2) and inventory trajectoriesgenerated to balance, not yet declared step by step
Documents, images and audionot started

LIMITATIONS.md keeps the full list. When a line leaves this table, it is because the brick that replaces it has passed all three tests.


The evidence

[The Gauntlet, assertions held per section. Misata holds 136 of 136. A Faker script with the same columns holds 78 of 136: it ties on keys, value domains, diamond joins, arithmetic and distributions, and loses on time ordering, status-implied fields, cross-table totals, geography, lifecycles, missingness, late arrivals and grids.]

The Gauntlet: 13 tables and 136 SQL assertions run by DuckDB. The Faker script ties wherever a fact is about one column at a time and loses wherever columns, rows or tables have to agree. Source, rerun with python -m benchmarks.gauntlet.


[Detection AUC, lower is better, 0.5 means a classifier cannot tell generated rows from real ones. Olist orders: Misata from a declared schema 0.63, Misata from a sentence 0.73, Faker script 0.78, SDV fitted to the real data 0.71, Misata mimic 0.54. NYC taxi trips: 0.75, 0.80, 0.79, 0.68 and 0.54.]

A gradient-boosted classifier tries to tell generated rows from real ones; mean of five seeds. Without seeing a real row, a declared schema beats SDV trained on the real orders. On taxi trips SDV wins: blind fares are a miss. Source, numbers in summary.json.


ClaimMeasuredReproduce
Valid by construction0 violations of keys, CHECK, UNIQUE, NOT NULL and self-reference trees on five DDL schemas. A per-column Faker script: 15,014 to 50,416 per schema. SDV's multi-table model trained on valid data: 3,055 to 5,557, and it refuses two of the fivepython -m benchmarks.validity_bench
Conformance345 SQL assertions in four suites, all held: the Gauntlet 136, the Warren 110, the Ledger 48, the Borough 51python -m benchmarks.gauntlet
Reproducibleidentical fingerprints on Linux, macOS and Windowspython -m benchmarks.golden
Scale10,000,000 rows across 14 tables in 36.3 s at 0.78 GB peak on an Apple M1, every foreign key checked afterwardspython benchmarks/bench_r3_volume.py --scale 10 --stream out/

What you can declare

Each declaration has a guarantee stated so you could test it, a list of the columns it owns, a cost and a refusal. LANGUAGE.md is the reference. All 25 declaration kinds are checked for feasibility before a row exists and re-checked on the rows afterwards; the ones marked ✓ are also in the DuckDB certificate.

DeclareWhat holdsCertificate
outcome_curvesa measure's total per period, to the cent✓
rate_curvesthe share of rows flagged per period, to the nearest row✓
group_shareseach group's share of a measure inside every period✓
balanced_ledgerdebits equal credits per entry, and the trial balance is zero✓
waterfallsopening plus movements equals closing, per component✓
relationships, min_children, partition_byno orphans, no childless parent, no key crossing a tenant✓
unique, inequality, col_range, choices, NOT NULL, DDL CHECKno row breaks the rule✓
lifecyclesa status implies its whole history: the right timestamps set, in order, and the rest null✓
dag_edges, graph_motifsno cycle outside a planted case; each ring, fan and chain at its exact count✓
stock_flowsinventory balances, chains period to period, and opens where declared✓
lte_parent, sum_lte_parenta child, or a parent's children together, stay under the parent's bound, without moving a declared total✓
time_grids, parent_time, parent_enda declared clock; a child after its parent existed, and inside its span✓
no_overlap, capacityan entity in one place at a time; no resource holding more than it can at any instant✓
roll-ups, scd2, bitemporal, when_thenparents equal their children, versions tile a timeline
outliers, typos, duplicates, missingness, late_arrivalsdirt at exact counts, with an answer key
retention, joint_distributions, event_logs, processescohort curves, joint margins, logs that agree with status

Underneath any declaration, foreign keys resolve by construction (parents are built before children), text agrees with its row, money is skewed, timestamps have a daily rhythm and a few customers place most of the orders, so the data looks like a business before you declare anything. A sentence picks one of 18 story domains, from SaaS and fintech to healthcare, logistics and streaming, each with its tables, keys and calibrated distributions.


Pick your entry point

You haveCallNotes
A sentencemisata.generate("A SaaS company with 1,000 customers", seed=42)fastest start; print the tables or misata explain to see what it built
YAML or a dictmisata.generate_from_schema(schema)every declaration holds, or it refuses
SQL DDLmisata.from_ddl(ddl) or misata generate -c schema.sqlNOT NULL, UNIQUE, composite keys, CHECK ranges and enums, self-references
A live databasemisata seed postgresql://...introspects, inserts in one transaction, never truncates unless you pass --truncate
A dbt projectmisata dbt-unit-test --select my_modelfixtures from manifest.json whose keys agree
Prisma or Djangomisata prisma-seed, misata.from_django(app_labels=[...])
More rows than memorymisata.generate_stream(schema)yields (table, batch); partitioned Parquet, one file per partition
A pipeline whose output must hit a numbermisata solve -c solve.yamldrives inputs through your real dbt or Python pipeline to a declared outcome, within a stated tolerance
A dataset to sharemisata publish data --hf you/namere-verifies, then uploads the data with its spec, certificate and verifier
Real rows to imitatemisata.mimic(df)fitted to real rows; not anonymous

Pass a seed every time. The same seed is the same data.


Twelve recipes

Each is a folder with the schema, the commands, and the check that proves the declaration held. tools/stranger.py --clean runs every command on every page from a freshly built wheel in a clean virtualenv before each release, so the numbers on the pages are what you will see.

RecipeForProof
Seed a dev databaseapp developers, coding agents22 invariants from three CREATE TABLE statements
Known-answer tests for dbtanalytics engineersa dbt test that catches the last-day-of-month bug
A warehouse before usersfounders, data leads291,000 rows of partitioned Parquet, 80 invariants
A demo tenantsales engineers, PMschurned and healthy accounts, a few whales; realism 0.96
An eval databaseAI labs, eval teamstext-to-SQL answers verified on three rotated databases
An agent sandboxagent buildersthe sandbox and the grader agree row for row
Load testingplatform engineershot keys and a daily curve, keys checked before replay
Fraud and AMLrisk and ML teamsan acyclic background and every ring at its declared count
Ledgers that reconcilefinance engineering, audittwo fiscal years, 52 invariants, trial balance zero
Take-home exerciseshiring teams, course authorsa different answer key per candidate, each verified
PII-free environmentsregulated teamsstaging data from production DDL, never production rows
Example data for docsDX, API and SDK teamsnested JSONL whose ids resolve across files

A status that implies its history

Declare the state machine once and every row's timestamps follow from its status. From the demo tenant:

mermaid
stateDiagram-v2    direction LR    [*] --> trialing    trialing --> active    trialing --> churned    active --> past_due    active --> churned    past_due --> churned
yaml
lifecycles:  - name: subscription    table: subscriptions    state_column: status    start_column: started_at    initial: trialing    states:      - {name: trialing}      - {name: active, timestamp: activated_at}      - {name: past_due, timestamp: past_due_at}      - {name: churned, timestamp: churned_at, terminal: true}    transitions:      - [trialing, active]      - [trialing, churned]      - [active, past_due]      - [active, churned]      - [past_due, churned]    weights: {trialing: 0.07, active: 0.6, past_due: 0.07, churned: 0.26}

Of 1,200 subscriptions, exactly 720 are active, 312 churned, 84 past due and 84 still trialing: weights are counts, allocated by largest remainder, not probabilities. Every past-due row has an activation date and a past-due date, in that order and after it started; no active row has a churn date. The certificate checks both, row by row. A state reachable several ways splits its rows across every route: of the 312 churned, 156 left during the trial, 78 after being active and 78 after falling past due.


When it says no

text
$ misata explain bad.yamlGeneration would refuse: 1 conflict(s).1 declaration cannot be satisfied together:  1. [orders.channel] group_shares on amount      declared shares sum to 1.500 (web=0.6, app=0.6, store=0.3); a share of a whole cannot exceed 1.0      → Suggestion: scale the shares so they sum to 1.0, or drop a group. Misata will not renormalise them for you, because the result would be a specification you did not writeNothing was generated. Change the declarations each conflict names; its remedy says how.

misata explain prints the planned rows, what each declaration will write, how many invariants the certificate will carry and every conflict, without generating anything. Every command takes --json and prints one envelope; exit code 3 means the declarations cannot all hold and nothing was written.


With a coding agent

The agent designs the schema, Misata does the arithmetic, and DuckDB checks it. An agent cannot certify its own output; this is how it gets an answer it did not write.

[Install in Cursor]

bash
claude mcp add misata -- uvx --from "misata[mcp]" misata-mcp   # Claude Codemisata mcp install --client all                                  # Cursor, Windsurf, Claude Desktop

Fifteen tools: generate_from_schema, generate_dataset, explain_schema, validate_yaml, inspect_schema, preview_story, list_domains, validate_domain, seed_database, create_sandbox, query_sandbox, solve_outcome, attest_dataset, audit_dataset and check_realism. AGENTS.md tells an agent which entry point fits which job and which claims the evidence supports.


What it is not

Misata imitates nothing unless you ask it to. It does not learn a joint distribution from your data. If you need a statistical twin of a specific private table, use a differentially private synthesizer with a stated epsilon. mimic fits to real rows and is not anonymous.

It is exact for what can be declared (sums, rates, shares, roll-ups, ledgers, keys, lifecycles) and not for arbitrary SQL. A model built on a window function or business logic Misata cannot invert still gets coherent, key-valid inputs, and you write the expected output. misata solve closes part of that gap by driving inputs through your real pipeline to a declared outcome within a stated tolerance, and the certificate says which tolerance.

Priors are English-first and US-leaning on money, and rates round to whole rows. LIMITATIONS.md has the full list and wins any argument with the rest of the docs.

ImperativeInferentialDeclarative
You give itcode that emits valuesreal data to fitfacts that must hold
It guaranteesnothing after the factresemblance, approximatelythe facts exactly, or a refusal
Foreign keysyour problemper table fine, across tables fragileby construction
Needs real datanoyesno
ExamplesFaker, Mockaroo, LLM seed scriptsSDV, learned synthesizersMisata

Cite

If Misata helps your work, cite the software. GitHub's Cite this repository button reads the same metadata from CITATION.cff.

bibtex
@software{rasin_misata,  title  = {Misata: declarative synthetic data},  author = {Rasin, Muhammed},  url    = {https://github.com/rasinmuhammed/misata}}

Contribute

bash
git clone https://github.com/rasinmuhammed/misata && cd misatauv sync --all-extras && uv run pytest -qpython tools/stranger.py --clean    # every documented command, from the built wheel

A capability earns a place only if it can be declared and verified. The conformance suites in benchmarks/ are the contract: write the assertion first, watch it fail, then make it pass. MIT licensed.

Source: README.md at commit be29088

Tools

0
SourceWeft started this package once in an isolated sandbox, without credentials, to list its tools. That failed: the server did not start.

Version history

3
  1. v0.9.8LatestOct 26, 2026
  2. v0.9.6.60Oct 1, 2026
  3. v0.8.7.2Sep 16, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts

Other Misata integrations