duckrun 0.3.16__tar.gz → 0.3.17.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. duckrun-0.3.17.dev2/PKG-INFO +356 -0
  2. duckrun-0.3.17.dev2/README.md +327 -0
  3. duckrun-0.3.17.dev2/dbt/adapters/duckrun/__version__.py +1 -0
  4. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/adapters/duckrun/delta_plugin.py +52 -41
  5. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/adapters/duckrun/engine.py +266 -20
  6. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/adapters/duckrun/impl.py +1 -25
  7. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/adapters/duckrun/remote.py +31 -0
  8. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/include/duckrun/macros/materializations/_delta_core.sql +8 -1
  9. duckrun-0.3.17.dev2/duckrun/__init__.py +16 -0
  10. duckrun-0.3.17.dev2/duckrun/auth.py +73 -0
  11. duckrun-0.3.17.dev2/duckrun/delta_table.py +201 -0
  12. duckrun-0.3.17.dev2/duckrun/session.py +517 -0
  13. duckrun-0.3.17.dev2/duckrun.egg-info/PKG-INFO +356 -0
  14. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/duckrun.egg-info/SOURCES.txt +5 -4
  15. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/duckrun.egg-info/requires.txt +7 -1
  16. duckrun-0.3.17.dev2/duckrun.egg-info/top_level.txt +2 -0
  17. duckrun-0.3.17.dev2/pyproject.toml +67 -0
  18. duckrun-0.3.16/PKG-INFO +0 -469
  19. duckrun-0.3.16/README.md +0 -445
  20. duckrun-0.3.16/dbt/adapters/duckrun/__version__.py +0 -1
  21. duckrun-0.3.16/duckrun.egg-info/PKG-INFO +0 -469
  22. duckrun-0.3.16/duckrun.egg-info/top_level.txt +0 -1
  23. duckrun-0.3.16/pyproject.toml +0 -53
  24. duckrun-0.3.16/tests/test_merge_spill.py +0 -395
  25. duckrun-0.3.16/tests/test_remote_secret_discovery.py +0 -157
  26. duckrun-0.3.16/tests/test_table_exists_guard.py +0 -188
  27. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/LICENSE +0 -0
  28. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/MANIFEST.in +0 -0
  29. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/adapters/duckrun/__init__.py +0 -0
  30. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/adapters/duckrun/credentials.py +0 -0
  31. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/adapters/duckrun/environment.py +0 -0
  32. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/adapters/duckrun/secret.py +0 -0
  33. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/include/duckrun/__init__.py +0 -0
  34. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/include/duckrun/dbt_project.yml +0 -0
  35. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/include/duckrun/macros/catalog.sql +0 -0
  36. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/include/duckrun/macros/materializations/delta.sql +0 -0
  37. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/include/duckrun/macros/materializations/incremental.sql +0 -0
  38. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/dbt/include/duckrun/macros/materializations/table.sql +0 -0
  39. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/duckrun.egg-info/dependency_links.txt +0 -0
  40. {duckrun-0.3.16 → duckrun-0.3.17.dev2}/setup.cfg +0 -0
@@ -0,0 +1,356 @@
1
+ Metadata-Version: 2.4
2
+ Name: duckrun
3
+ Version: 0.3.17.dev2
4
+ Summary: A dbt adapter that runs SQL in DuckDB and materializes to Delta Lake (delta_rs).
5
+ Author: mim
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/djouallah/duckrun
8
+ Project-URL: Repository, https://github.com/djouallah/duckrun
9
+ Project-URL: Issues, https://github.com/djouallah/duckrun/issues
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: dbt-duckdb>=1.8
14
+ Requires-Dist: dbt-core<2.0,>=1.8
15
+ Requires-Dist: duckdb==1.5.4.dev18
16
+ Requires-Dist: deltalake<1.5.1,>=1.5.0
17
+ Requires-Dist: requests
18
+ Provides-Extra: local
19
+ Requires-Dist: azure-identity; extra == "local"
20
+ Provides-Extra: test
21
+ Requires-Dist: pytest>=7; extra == "test"
22
+ Requires-Dist: pyarrow; extra == "test"
23
+ Requires-Dist: pandas; extra == "test"
24
+ Provides-Extra: conformance
25
+ Requires-Dist: pytest>=7; extra == "conformance"
26
+ Requires-Dist: pyarrow; extra == "conformance"
27
+ Requires-Dist: dbt-tests-adapter; extra == "conformance"
28
+ Dynamic: license-file
29
+
30
+ <img src="https://raw.githubusercontent.com/djouallah/duckrun/main/duckrun.png" width="400" alt="duckrun">
31
+
32
+ [![PyPI version](https://badge.fury.io/py/duckrun.svg)](https://badge.fury.io/py/duckrun)
33
+
34
+ > **Disclaimer:** This is a personal project, built and maintained in my own time. It is
35
+ > not affiliated with, endorsed by, or supported by any employer or vendor. No warranty —
36
+ > use it at your own risk.
37
+
38
+ **duckrun** is a [dbt](https://www.getdbt.com/) adapter that runs your model SQL in
39
+ **DuckDB** and writes the results to **Delta Lake** using
40
+ [`delta_rs`](https://delta-io.github.io/delta-rs/) (the `deltalake` Python package).
41
+ duckrun itself is just glue — it owns none of the heavy lifting. The real work is done
42
+ by **DuckDB** (executes the SQL), **delta-rs** (writes the Delta table), **Arrow** (the
43
+ zero-copy (kind of) bridge that hands query results from DuckDB to delta-rs), and **dbt** (orchestrates
44
+ the DAG). DuckDB is here for convenience as the SQL engine; the materialization is all
45
+ delta-rs and Arrow.
46
+
47
+ It is a thin wrapper around [`dbt-duckdb`](https://github.com/duckdb/dbt-duckdb). You
48
+ keep everything dbt-duckdb gives you — views, seeds, sources, tests, snapshots, the full
49
+ plugin ecosystem — and gain one extra thing: a Delta-backed `table` / `incremental`
50
+ materialization that writes real Delta tables.
51
+
52
+ The design rationale — why delta_rs and not DuckDB's native Delta writer, why Delta and not
53
+ Iceberg, why a separate adapter — lives in [docs/design_document.md](docs/design_document.md).
54
+
55
+ ## How it fits together
56
+
57
+ DuckDB is a great query engine, Delta Lake is a great open table format, and dbt is the
58
+ right tool to orchestrate the DAG. duckrun wires the three together:
59
+
60
+ > **DuckDB executes · delta_rs materializes · dbt orchestrates.**
61
+
62
+ ## Install
63
+
64
+ ```bash
65
+ pip install duckrun
66
+ ```
67
+
68
+ That single install pulls in `dbt-duckdb` (and therefore `duckdb`) plus `deltalake`.
69
+
70
+ ## Configure your profile
71
+
72
+ ```yaml
73
+ # ~/.dbt/profiles.yml
74
+ my_project:
75
+ target: dev
76
+ outputs:
77
+ dev:
78
+ type: duckrun
79
+ # No `threads:` needed — duckrun always runs single-threaded.
80
+ # DuckDB runs in-memory by default — the Delta tables are the only state.
81
+ # Default Delta location for models that don't set config(location=...).
82
+ root_path: './warehouse' # local path, or s3://..., gs://..., abfss://...
83
+ # storage_options: {} # passed through to deltalake for remote stores
84
+ ```
85
+
86
+ Persisted models are written to `<root_path>/<schema>/<model>` (e.g.
87
+ `./warehouse/dbo/orders`), or to an explicit `config(location=...)`.
88
+
89
+ ### Fabric Lakehouse without a schema
90
+
91
+ A schema-less Lakehouse (tables straight under `Tables/`, no `Tables/<schema>/` grouping) is
92
+ a **bad pattern** — you lose the namespace that keeps a warehouse organized — but if you're
93
+ stuck with one, no special config is needed. Drop the trailing `Tables` from `root_path` and
94
+ let the schema fill that slot:
95
+
96
+ ```yaml
97
+ schema: Tables
98
+ root_path: "abfss://<ws>@onelake.dfs.fabric.microsoft.com/<lh>.Lakehouse"
99
+ ```
100
+
101
+ Since models are written to `<root_path>/<schema>/<model>`, this lands them at
102
+ `<lh>.Lakehouse/Tables/<model>` — exactly the flat layout the schema-less Lakehouse expects.
103
+ Prefer a schema-enabled Lakehouse (`root_path: .../Tables`, real schemas) whenever you can.
104
+
105
+ ### Remote stores (S3 / GCS / ADLS)
106
+
107
+ Point `root_path` at the warehouse location and pass credentials through
108
+ `storage_options` — these flow straight to deltalake for writes and merges.
109
+
110
+ On Azure-backed stores, if `storage_options` carries a `bearer_token` (or `token` /
111
+ `access_token`), the adapter also auto-creates a matching DuckDB Azure secret, so
112
+ `delta_scan()` reads work with no extra config. In a notebook where the storage secret is
113
+ already provided to DuckDB, you can leave `storage_options` empty.
114
+
115
+ ```yaml
116
+ remote:
117
+ type: duckrun
118
+ schema: dbo
119
+ root_path: "s3://my-bucket/warehouse" # or abfss://... , gs://...
120
+ storage_options:
121
+ aws_access_key_id: "{{ env_var('AWS_ACCESS_KEY_ID') }}"
122
+ aws_secret_access_key: "{{ env_var('AWS_SECRET_ACCESS_KEY') }}"
123
+ ```
124
+
125
+ Verified end-to-end against real remote object storage: `table` overwrite, `incremental`
126
+ merge, and `delta_scan` reads / tests.
127
+
128
+ ## Materializations
129
+
130
+ | materialized | backed by | notes |
131
+ |-------------------|--------------------------|-----------------------------------------------------------------------|
132
+ | **`table`** | Delta (overwrite) | DuckDB runs the SQL; delta_rs writes the table fresh each run. |
133
+ | **`incremental`** | Delta (merge / append) | First run overwrites; later runs apply `incremental_strategy`. |
134
+ | `view` | in-memory DuckDB | Ephemeral staging within a run (inherited from dbt-duckdb). |
135
+ | `seed` | in-memory DuckDB | CSV fixtures (inherited from dbt-duckdb). |
136
+ | `delta` | Delta | Alias for `table`; honors `incremental=true`. Kept for convenience. |
137
+
138
+ The persisted materializations (`table`, `incremental`, `delta`) register a `delta_scan`
139
+ view over the new Delta table, so downstream `ref()` works.
140
+
141
+ ### `table`
142
+
143
+ ```sql
144
+ -- models/orders.sql
145
+ {{ config(materialized='table') }}
146
+
147
+ select status, count(*) as n, sum(amount) as total
148
+ from {{ ref('stg_orders') }}
149
+ group by status
150
+ ```
151
+
152
+ ### `incremental`
153
+
154
+ ```sql
155
+ {{ config(materialized='incremental', unique_key='order_id', incremental_strategy='merge') }}
156
+
157
+ select * from {{ ref('stg_orders') }}
158
+ {% if is_incremental() %}
159
+ where updated_at > (select max(updated_at) from {{ this }})
160
+ {% endif %}
161
+ ```
162
+
163
+ The first run (or `--full-refresh`, or a missing table) overwrites. Later runs apply the
164
+ `incremental_strategy`:
165
+
166
+ | `incremental_strategy` | behavior | requires |
167
+ |------------------------------------|-------------------------------------------|--------------|
168
+ | `merge` (default with `unique_key`) | upsert — update matched, insert new | `unique_key` |
169
+ | `insert` | insert only new keys (idempotent append) | `unique_key` |
170
+ | `append` (default without `unique_key`) | blind append | — |
171
+ | `safeappend` | append, but only if the table is unchanged since the model read it (else fail) — cheap, no dedup scan | — |
172
+
173
+ ### `safeappend`
174
+
175
+ A cheap append for the common "load only what's new" pattern — when your model SQL **already
176
+ guarantees no duplicates** and you don't want to pay for a merge.
177
+
178
+ ```sql
179
+ {{ config(materialized='incremental', incremental_strategy='safeappend') }}
180
+
181
+ select * from read_csv(getvariable('new_files'))
182
+ {% if is_incremental() %}
183
+ -- the dedup is your SQL's job: only load files not already in the table
184
+ where file not in (select distinct file from {{ this }})
185
+ {% endif %}
186
+ ```
187
+
188
+ **Why, reason 1 — performance.** `merge` / `insert` scan the target and join on the key to find
189
+ what's new — expensive on a large table. If the SQL above already excludes rows that are present,
190
+ that work is redundant. `safeappend` is a plain append: **no target data scan, no key join, and
191
+ DuckDB keeps its full memory budget** (the merge memory split is never applied — same as `append`
192
+ / `overwrite`). The only thing it reads from the target is one Delta log entry to get the version.
193
+
194
+ **Why, reason 2 — a concurrency guard a blind `append` doesn't have.** Because the dedup is done
195
+ in SQL against `{{ this }}`, a plain `append` is unsafe under concurrency: if another writer
196
+ commits between your `not in (... from {{ this }})` read and your write, the file it added isn't
197
+ excluded and you get a duplicate. `safeappend` closes that gap — it commits **only if the table
198
+ version is unchanged since the model started** (captured *before* it reads `{{ this }}`); if
199
+ anything committed in between, it fails with `CommitFailedError` so the run re-runs against the new
200
+ state. No duplicate slips in.
201
+
202
+ This is **optimistic concurrency control** — it never locks the table or blocks other writers; it
203
+ appends, then validates at commit with a compare-and-swap on the version and aborts on a mismatch.
204
+ Its policy is the strictest of the strategies (abort on *any* concurrent change, rather than
205
+ reconcile like `merge` or auto-rebase like `append`), but the mechanism is optimistic, not
206
+ pessimistic. Re-running is safe and idempotent: the SQL dedup simply excludes whatever the previous
207
+ attempt already loaded.
208
+
209
+ First run (or `--full-refresh`, or a missing table) overwrites to create the table; `safeappend`
210
+ applies on later runs. A real example is the AEMO
211
+ [`fct_scada`](tests/integration_tests/aemo/models/marts/fct_scada.sql) model — the project's largest table,
212
+ which loads only not-yet-seen files and so uses `safeappend` instead of an expensive merge.
213
+
214
+ ### Config options (`table` / `incremental` / `delta`)
215
+
216
+ | option | description |
217
+ |-------------------------|-----------------------------------------------------------------------------|
218
+ | `location` | Delta path. Defaults to `<root_path>/<schema>/<id>`. |
219
+ | `incremental_strategy` | `merge` \| `insert` \| `append` \| `safeappend` (incremental only). |
220
+ | `unique_key` | column(s) to merge on. |
221
+ | `merge_update_columns` | merge: update only these columns on match (others untouched). |
222
+ | `merge_exclude_columns` | merge: update all columns **except** these on match. |
223
+ | `merge_max_spill_size` | merge: memory ceiling in **bytes** for delta_rs's merge pool (not a disk budget). Defaults to ~60% of the **effective** limit — `min(physical RAM, container/cgroup limit, currently-free RAM)` — beyond which delta_rs spills the merge join to disk (like DuckDB's `memory_limit`). The other big consumer, DuckDB itself, is separately pinned to ~30% of the same effective limit on the merge path (it produces the merge source in the same process), so the two budgets sum under the cgroup cap; both log their chosen value at run start. Set `0` to disable. It bounds the merge pool, *not* the whole process (the Arrow source, read buffers, and spill-file page cache sit outside it), so on a tight container with a huge source the total can still exceed the cap — lower it if needed. A cap below the join's minimum (~hundreds of MB) makes the merge raise `Resources exhausted` instead of spilling. Requires deltalake 1.5.0 (pinned). |
224
+ | `incremental_predicates`| merge: extra predicates AND-ed into the merge condition (use `target.`/`source.`, or dbt's `DBT_INTERNAL_DEST`/`DBT_INTERNAL_SOURCE`). |
225
+ | `on_schema_change` | `ignore` (default) \| `append_new_columns` \| `fail`. (`sync_all_columns` only *adds* — delta_rs can't drop columns.) |
226
+ | `partition_by` | Delta partition column(s). |
227
+ | `merge_schema` | allow schema evolution on write. |
228
+ | `storage_options` | per-model override forwarded to deltalake. |
229
+
230
+ ## Reading existing tables/files as sources
231
+
232
+ A source routed to the `duckrun` plugin can be a Delta table, a CSV, or a Parquet file.
233
+ `delta_table_path` always reads Delta; otherwise the path comes from `location` and the
234
+ format is taken from `format` (`csv` | `parquet` | `delta`) or inferred from the extension.
235
+
236
+ ```yaml
237
+ sources:
238
+ - name: lake
239
+ tables:
240
+ - name: customers # Delta table
241
+ meta:
242
+ plugin: duckrun
243
+ delta_table_path: 's3://bucket/lake/customers'
244
+ - name: events # CSV (read_csv_auto)
245
+ meta:
246
+ plugin: duckrun
247
+ format: csv
248
+ location: 's3://bucket/raw/events.csv'
249
+ - name: metrics # Parquet
250
+ meta:
251
+ plugin: duckrun
252
+ format: parquet
253
+ location: 's3://bucket/raw/metrics.parquet'
254
+ ```
255
+
256
+ ## How it works
257
+
258
+ 1. dbt compiles your model SQL.
259
+ 2. The materialization stages it as a DuckDB view.
260
+ 3. A `dbt-duckdb` plugin (a `store()` hook) hands that relation to deltalake over the
261
+ Arrow C-stream interface (`__arrow_c_stream__`) — no pyarrow required — which
262
+ `write_deltalake` / `DeltaTable.merge` consume natively.
263
+ 4. The model relation becomes a `delta_scan` view over the new Delta table.
264
+
265
+ The adapter is a thin subclass of dbt-duckdb declaring `dependencies=['duckdb']`, so
266
+ `view`, `seed`, tests, and the rest are inherited directly; only `table` and
267
+ `incremental` are overridden to write Delta.
268
+
269
+ ## Table maintenance (compaction & vacuum)
270
+
271
+ **duckrun maintains your Delta tables automatically — no configuration, no scheduled job, no
272
+ separate `OPTIMIZE`/`VACUUM` run to remember.** It happens inline on every write.
273
+
274
+ This matters because delta_rs has **no** automatic, post-commit maintenance of its own — and it
275
+ ignores Databricks-style auto-optimize table properties (`delta.autoOptimize.*`). Left alone, an
276
+ incremental table fragments into many small Parquet files and keeps every superseded file version
277
+ forever. duckrun runs the maintenance for you, right after each write:
278
+
279
+ | write | maintenance |
280
+ |---|---|
281
+ | `table` / overwrite | `vacuum` + metadata cleanup every run |
282
+ | `append` | `optimize.compact` + `vacuum` + cleanup once the table exceeds **100 files** |
283
+ | `merge` / `insert` | same threshold-gated `compact` + `vacuum` + cleanup after the merge |
284
+ | `microbatch` / delete+insert | same threshold-gated maintenance |
285
+
286
+ Every `vacuum` uses delta_rs's **safe default retention (7 days / 168h)**, so files a
287
+ concurrent reader might still be reading are never deleted out from under it. The trade-off
288
+ is that a superseded file version lingers for the retention window before it can be
289
+ reclaimed — duckrun favors read-safety over immediate disk savings.
290
+
291
+ ## Connection API (notebook)
292
+
293
+ Besides the dbt adapter, duckrun ships a storage-neutral, PySpark-shaped `duckrun.connect()` for
294
+ interactive/notebook use (local, S3, GCS, ADLS, OneLake). `conn.sql(...)` is **read-only** (including
295
+ time travel — `delta_scan('…', version => N)`); writes go through the Spark surface: a `DataFrame`
296
+ with `.write…saveAsTable()` (modes `overwrite` / `append` / `safeappend` / `ignore`) and a `DeltaTable` handle
297
+ (`conn.delta_table(name)` / `DeltaTable.forName`) with `.merge(...)`, `.delete()`, `.update()`,
298
+ `.replaceWhere()`, `.version()`, plus `conn.read` and `conn.catalog`.
299
+
300
+ `merge` is **snapshot-pinned by default** — Spark's single-snapshot MERGE, with no extra arguments:
301
+ the target version is captured and the commit is validated against it, so a concurrent writer fails
302
+ the commit loudly instead of silently interleaving. `mode("safeappend")` is the same optimistic,
303
+ fail-loud append as the dbt [`safeappend`](#safeappend) strategy: it commits only if the table is
304
+ unchanged since the call, else raises `CommitFailedError`.
305
+
306
+ ```python
307
+ import duckrun
308
+ conn = duckrun.connect("abfss://ws@onelake.dfs.fabric.microsoft.com/lh.Lakehouse/Tables/dbo")
309
+ conn.sql("select * from orders").write.mode("overwrite").saveAsTable("orders_copy")
310
+ conn.table("orders_copy").show()
311
+
312
+ conn.delta_table("orders").delete("region = 'eu'") # delete / update / replaceWhere
313
+
314
+ # upsert — pinned automatically, nothing to pass
315
+ src = conn.sql("select * from updates")
316
+ conn.delta_table("orders").merge(src, "target.id = source.id") \
317
+ .whenMatchedUpdateAll().whenNotMatchedInsertAll().execute()
318
+ ```
319
+
320
+ See [docs/connection-api.md](docs/connection-api.md) for the full per-method scorecard.
321
+
322
+ ## Building with an AI assistant
323
+
324
+ duckrun ships a guide for AI coding assistants so they get the adapter's defaults right
325
+ (several differ from other dbt adapters). If you use **Claude Code**, install it once and
326
+ it loads on demand when you ask a duckrun question:
327
+
328
+ ```
329
+ /plugin marketplace add djouallah/duckrun
330
+ /plugin install duckrun-projects@duckrun
331
+ ```
332
+
333
+ Using a different assistant (Cursor, Copilot, Codex, …) or just have the repo checked
334
+ out? It reads the [`AGENTS.md`](AGENTS.md) at the repo root automatically, which points to
335
+ the full guide in
336
+ [`plugins/duckrun-projects/skills/duckrun-projects/SKILL.md`](plugins/duckrun-projects/skills/duckrun-projects/SKILL.md).
337
+ None of this is required to use duckrun — `pip install duckrun` is unaffected.
338
+
339
+ ## Docs & test results
340
+
341
+ | Doc | What's in it |
342
+ |---|---|
343
+ | [Design document](docs/design_document.md) | Why delta_rs (not DuckDB's native Delta writer), why Delta (not Iceberg), why a separate adapter. |
344
+ | [Connection API](docs/connection-api.md) | The `duckrun.connect()` notebook API + the live per-method scorecard. |
345
+ | [dbt adapter conformance](docs/conformance.md) | Official `dbt-tests-adapter` results, regenerated on every push to `main`. |
346
+ | [Incremental MERGE benchmark](docs/merge-benchmark.md) | ~120M-row TPCH merge / append / overwrite scorecard — the release gate. |
347
+
348
+ **Testing.** `tests/integration_tests/aemo/` is a small dbt project built against OneLake, and
349
+ `tests/integration_tests/coffee/` is the connection-API coffee-shop scenario / stress test (CI:
350
+ [`integration.yml`](.github/workflows/integration.yml)); `tests/conformance/`
351
+ runs the official suite (above); `tests/correctness/` proves the concurrency guarantees. The cards
352
+ in those docs are rendered live by CI, so they always reflect the latest `main`.
353
+
354
+ ## License
355
+
356
+ MIT