duckstring 0.5.0__tar.gz → 0.6.0__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 (288) hide show
  1. duckstring-0.6.0/CHANGELOG.md +197 -0
  2. {duckstring-0.5.0/src/duckstring.egg-info → duckstring-0.6.0}/PKG-INFO +80 -54
  3. duckstring-0.6.0/README.md +111 -0
  4. {duckstring-0.5.0 → duckstring-0.6.0}/pyproject.toml +15 -11
  5. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/alerts/base.py +22 -4
  6. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/alerts/email.py +9 -1
  7. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/app.py +1 -1
  8. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/auth.py +37 -11
  9. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/data_lease.py +5 -6
  10. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/db.py +20 -1
  11. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/driver.py +171 -30
  12. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/ec2_launcher.py +22 -4
  13. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/egress_worker.py +5 -4
  14. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/fargate_launcher.py +6 -0
  15. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/launcher.py +19 -9
  16. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/poller.py +2 -1
  17. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/pool_launcher.py +2 -8
  18. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/catchment.py +29 -8
  19. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/data.py +37 -22
  20. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/deploy.py +91 -120
  21. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/draw.py +24 -3
  22. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/duct.py +22 -2
  23. duckstring-0.6.0/src/duckstring/catchment/schema/029_source_pins.sql +5 -0
  24. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/serving.py +0 -1
  25. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/404.html +1 -1
  26. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/__next.__PAGE__.txt +2 -2
  27. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/__next._full.txt +2 -2
  28. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/__next._head.txt +1 -1
  29. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/__next._index.txt +1 -1
  30. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/__next._tree.txt +1 -1
  31. duckstring-0.5.0/src/duckstring/catchment/static/_next/static/chunks/15o1yi9qh60on.js → duckstring-0.6.0/src/duckstring/catchment/static/_next/static/chunks/09y-jd_5pt5my.js +1 -1
  32. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_not-found/__next._full.txt +1 -1
  33. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_not-found/__next._head.txt +1 -1
  34. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_not-found/__next._index.txt +1 -1
  35. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_not-found/__next._not-found.__PAGE__.txt +1 -1
  36. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_not-found/__next._not-found.txt +1 -1
  37. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_not-found/__next._tree.txt +1 -1
  38. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_not-found.html +1 -1
  39. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_not-found.txt +1 -1
  40. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/index.html +1 -1
  41. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/index.txt +2 -2
  42. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/__init__.py +2 -2
  43. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/_http.py +4 -0
  44. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/alert.py +17 -17
  45. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/bulk.py +22 -18
  46. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/catchment.py +80 -51
  47. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/control.py +41 -30
  48. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/data.py +17 -10
  49. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/deploy.py +85 -34
  50. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/duck.py +31 -22
  51. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/duct.py +16 -15
  52. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/lineage.py +9 -9
  53. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/pond.py +111 -54
  54. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/puddle.py +12 -4
  55. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/secret.py +5 -5
  56. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/serve.py +12 -9
  57. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/spout.py +19 -17
  58. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/status.py +2 -2
  59. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/trigger.py +24 -15
  60. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/window.py +12 -10
  61. duckstring-0.6.0/src/duckstring/core.py +1123 -0
  62. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/dataplane.py +448 -158
  63. duckstring-0.6.0/src/duckstring/demo/sql_reports/README.md +11 -0
  64. duckstring-0.6.0/src/duckstring/demo/sql_reports/pond.toml +11 -0
  65. duckstring-0.6.0/src/duckstring/demo/sql_reports/sql/monthly_summary.sql +11 -0
  66. duckstring-0.6.0/src/duckstring/demo/sql_sales/README.md +11 -0
  67. duckstring-0.6.0/src/duckstring/demo/sql_sales/data/tier_bands.csv +4 -0
  68. duckstring-0.6.0/src/duckstring/demo/sql_sales/pond.toml +22 -0
  69. duckstring-0.6.0/src/duckstring/demo/sql_sales/sql/daily_sales.sql +9 -0
  70. duckstring-0.6.0/src/duckstring/demo/sql_sales/sql/price_tiers.sql +9 -0
  71. duckstring-0.6.0/src/duckstring/demo/sql_sales/sql/sale_line.sql +12 -0
  72. duckstring-0.6.0/src/duckstring/demo/sql_sales/src/puddles.py +47 -0
  73. duckstring-0.6.0/src/duckstring/discover.py +170 -0
  74. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/duck/__main__.py +116 -5
  75. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/duck/client.py +7 -0
  76. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/duck/dbt_executor.py +8 -4
  77. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/duck/executor.py +145 -50
  78. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/duck/pool_agent.py +22 -2
  79. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/egress/base.py +1 -1
  80. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/egress/credentials.py +13 -0
  81. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/egress/destination.py +21 -3
  82. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/egress/object_store.py +32 -7
  83. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/egress/postgres.py +1 -1
  84. duckstring-0.6.0/src/duckstring/environments.py +245 -0
  85. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/flock/__init__.py +62 -11
  86. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/flock/engines/athena.py +3 -2
  87. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/local/hydrate.py +11 -5
  88. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/local/runner.py +18 -41
  89. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/objects.py +5 -2
  90. duckstring-0.6.0/src/duckstring/pondignore.py +79 -0
  91. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/schema_contract.py +4 -1
  92. duckstring-0.6.0/src/duckstring/sql_ripples.py +420 -0
  93. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/storage.py +11 -68
  94. duckstring-0.6.0/src/duckstring/trickle/acc.py +122 -0
  95. duckstring-0.6.0/src/duckstring/trickle/agg.py +184 -0
  96. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/trickle/builder.py +124 -95
  97. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/trickle/capture.py +20 -41
  98. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/trickle/io.py +204 -50
  99. {duckstring-0.5.0 → duckstring-0.6.0/src/duckstring.egg-info}/PKG-INFO +80 -54
  100. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring.egg-info/SOURCES.txt +19 -6
  101. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring.egg-info/requires.txt +3 -4
  102. duckstring-0.5.0/CHANGELOG.md +0 -93
  103. duckstring-0.5.0/README.md +0 -85
  104. duckstring-0.5.0/src/duckstring/core.py +0 -728
  105. duckstring-0.5.0/src/duckstring/iceberg_catalog.py +0 -227
  106. duckstring-0.5.0/src/duckstring/iceberg_plane.py +0 -297
  107. duckstring-0.5.0/src/duckstring/trickle/acc.py +0 -139
  108. duckstring-0.5.0/src/duckstring/trickle/agg.py +0 -209
  109. {duckstring-0.5.0 → duckstring-0.6.0}/LICENSE +0 -0
  110. {duckstring-0.5.0 → duckstring-0.6.0}/MANIFEST.in +0 -0
  111. {duckstring-0.5.0 → duckstring-0.6.0}/setup.cfg +0 -0
  112. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/__init__.py +0 -0
  113. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/__main__.py +0 -0
  114. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/acc.py +0 -0
  115. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/agg.py +0 -0
  116. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/alerts/__init__.py +0 -0
  117. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/alerts/event.py +0 -0
  118. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/alerts/webhook.py +0 -0
  119. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/__init__.py +0 -0
  120. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/alert_worker.py +0 -0
  121. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/asgi.py +0 -0
  122. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/cloud.py +0 -0
  123. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/cloud_backends.py +0 -0
  124. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/cloud_deploy.py +0 -0
  125. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/dag.py +0 -0
  126. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/dialback.py +0 -0
  127. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/flight_sql.py +0 -0
  128. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/pg_wire.py +0 -0
  129. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/registry.py +0 -0
  130. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/relay.py +0 -0
  131. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/__init__.py +0 -0
  132. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/alerts.py +0 -0
  133. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/duck.py +0 -0
  134. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/metrics.py +0 -0
  135. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/orchestrate.py +0 -0
  136. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/pool.py +0 -0
  137. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/secrets.py +0 -0
  138. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/serving.py +0 -0
  139. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/routes/view.py +0 -0
  140. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/001_init.sql +0 -0
  141. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/002_ducts.sql +0 -0
  142. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/003_pull_m.sql +0 -0
  143. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/004_identity.sql +0 -0
  144. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/005_pond_version_schema.sql +0 -0
  145. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/006_refresh.sql +0 -0
  146. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/007_catchment_key.sql +0 -0
  147. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/008_spout.sql +0 -0
  148. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/009_spout_state.sql +0 -0
  149. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/010_spout_wake.sql +0 -0
  150. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/011_spout_window.sql +0 -0
  151. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/012_spout_node.sql +0 -0
  152. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/013_changed_f.sql +0 -0
  153. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/014_alert.sql +0 -0
  154. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/016_alert_scope_major.sql +0 -0
  155. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/017_alert_renotify.sql +0 -0
  156. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/018_duck.sql +0 -0
  157. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/019_lineage.sql +0 -0
  158. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/020_column_lineage.sql +0 -0
  159. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/021_cloud.sql +0 -0
  160. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/022_retire_duck_size.sql +0 -0
  161. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/023_pool_provider.sql +0 -0
  162. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/024_serving.sql +0 -0
  163. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/025_dbt.sql +0 -0
  164. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/026_deploy_config.sql +0 -0
  165. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/027_persist.sql +0 -0
  166. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/schema/028_persist_timing.sql +0 -0
  167. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/secrets.py +0 -0
  168. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/state_sync.py +0 -0
  169. {duckstring-0.5.0/src/duckstring/catchment/static/_next/static/7b3BoSoT7Zw6j1d3GFlHs → duckstring-0.6.0/src/duckstring/catchment/static/_next/static/IexTU6uXUggN4m637L3i4}/_buildManifest.js +0 -0
  170. {duckstring-0.5.0/src/duckstring/catchment/static/_next/static/7b3BoSoT7Zw6j1d3GFlHs → duckstring-0.6.0/src/duckstring/catchment/static/_next/static/IexTU6uXUggN4m637L3i4}/_clientMiddlewareManifest.js +0 -0
  171. {duckstring-0.5.0/src/duckstring/catchment/static/_next/static/7b3BoSoT7Zw6j1d3GFlHs → duckstring-0.6.0/src/duckstring/catchment/static/_next/static/IexTU6uXUggN4m637L3i4}/_ssgManifest.js +0 -0
  172. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_next/static/chunks/03~yq9q893hmn.js +0 -0
  173. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_next/static/chunks/07lhk_q6pmm3r.js +0 -0
  174. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_next/static/chunks/0_8xp0_1~w7l7.css +0 -0
  175. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_next/static/chunks/0dbhjjzl8qfwv.js +0 -0
  176. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_next/static/chunks/0jvmviuftg5e2.css +0 -0
  177. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_next/static/chunks/0kczw6usu-y-f.js +0 -0
  178. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_next/static/chunks/11kjahy2ntf0n.js +0 -0
  179. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/_next/static/chunks/turbopack-03~mbvk_uplk_.js +0 -0
  180. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/favicon.svg +0 -0
  181. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/logo-mark.svg +0 -0
  182. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/catchment/static/logo.svg +0 -0
  183. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/cli/config.py +0 -0
  184. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/dbt_mode.py +0 -0
  185. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/catalog/.gitignore +0 -0
  186. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/catalog/README.md +0 -0
  187. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/catalog/pond.toml +0 -0
  188. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/catalog/src/pond.py +0 -0
  189. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_actors/README.md +0 -0
  190. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_actors/pond.toml +0 -0
  191. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_actors/src/pond.py +0 -0
  192. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_events/README.md +0 -0
  193. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_events/pond.toml +0 -0
  194. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_events/src/pond.py +0 -0
  195. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_pushes/README.md +0 -0
  196. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_pushes/pond.toml +0 -0
  197. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_pushes/src/pond.py +0 -0
  198. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_repo_activity/README.md +0 -0
  199. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_repo_activity/pond.toml +0 -0
  200. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_repo_activity/src/pond.py +0 -0
  201. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_stars/README.md +0 -0
  202. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_stars/pond.toml +0 -0
  203. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_stars/src/pond.py +0 -0
  204. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_trending/README.md +0 -0
  205. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_trending/pond.toml +0 -0
  206. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/gh_trending/src/pond.py +0 -0
  207. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/orders/.gitignore +0 -0
  208. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/orders/README.md +0 -0
  209. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/orders/pond.toml +0 -0
  210. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/orders/src/pond.py +0 -0
  211. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/priced/.gitignore +0 -0
  212. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/priced/README.md +0 -0
  213. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/priced/pond.toml +0 -0
  214. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/priced/src/pond.py +0 -0
  215. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/priced/src/puddles.py +0 -0
  216. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/products/.gitignore +0 -0
  217. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/products/README.md +0 -0
  218. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/products/pond.toml +0 -0
  219. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/products/src/pond.py +0 -0
  220. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/reports/.gitignore +0 -0
  221. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/reports/README.md +0 -0
  222. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/reports/pond.toml +0 -0
  223. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/reports/src/pond.py +0 -0
  224. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/revenue/.gitignore +0 -0
  225. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/revenue/README.md +0 -0
  226. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/revenue/pond.toml +0 -0
  227. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/revenue/src/pond.py +0 -0
  228. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/revenue/src/puddles.py +0 -0
  229. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/sales/.gitignore +0 -0
  230. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/sales/README.md +0 -0
  231. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/sales/pond.toml +0 -0
  232. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/sales/src/pond.py +0 -0
  233. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/sales/src/puddles.py +0 -0
  234. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_analytics/.gitignore +0 -0
  235. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_analytics/README.md +0 -0
  236. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_analytics/dbt/dbt_project.yml +0 -0
  237. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_analytics/dbt/models/orders_clean.sql +0 -0
  238. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_analytics/dbt/models/revenue_by_product.sql +0 -0
  239. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_analytics/dbt/models/sources.yml +0 -0
  240. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_analytics/dbt/models/top_products.sql +0 -0
  241. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_analytics/pond.toml +0 -0
  242. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_orders/.gitignore +0 -0
  243. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_orders/README.md +0 -0
  244. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_orders/pond.toml +0 -0
  245. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/shop_orders/src/pond.py +0 -0
  246. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_category_revenue/README.md +0 -0
  247. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_category_revenue/pond.toml +0 -0
  248. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_category_revenue/src/pond.py +0 -0
  249. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_items/README.md +0 -0
  250. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_items/pond.toml +0 -0
  251. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_items/src/pond.py +0 -0
  252. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_priced/README.md +0 -0
  253. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_priced/pond.toml +0 -0
  254. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_priced/src/pond.py +0 -0
  255. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_sales/README.md +0 -0
  256. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_sales/pond.toml +0 -0
  257. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_sales/src/pond.py +0 -0
  258. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_store_revenue/README.md +0 -0
  259. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_store_revenue/pond.toml +0 -0
  260. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_store_revenue/src/pond.py +0 -0
  261. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_stores/README.md +0 -0
  262. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_stores/pond.toml +0 -0
  263. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/tpcds_stores/src/pond.py +0 -0
  264. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/transactions/.gitignore +0 -0
  265. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/transactions/README.md +0 -0
  266. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/transactions/pond.toml +0 -0
  267. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/demo/transactions/src/pond.py +0 -0
  268. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/duck/__init__.py +0 -0
  269. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/duck/core.py +0 -0
  270. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/egress/__init__.py +0 -0
  271. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/engine/__init__.py +0 -0
  272. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/engine/catchment.py +0 -0
  273. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/engine/core.py +0 -0
  274. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/engine/pond.py +0 -0
  275. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/engine/worker.py +0 -0
  276. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/flock/engines/__init__.py +0 -0
  277. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/flock/equivalence.py +0 -0
  278. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/keys.py +0 -0
  279. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/local/__init__.py +0 -0
  280. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/local/project.py +0 -0
  281. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/trickle/__init__.py +0 -0
  282. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/trickle/context.py +0 -0
  283. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/trickle/lineage.py +0 -0
  284. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/trickle_builder.py +0 -0
  285. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring/trickle_io.py +0 -0
  286. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring.egg-info/dependency_links.txt +0 -0
  287. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring.egg-info/entry_points.txt +0 -0
  288. {duckstring-0.5.0 → duckstring-0.6.0}/src/duckstring.egg-info/top_level.txt +0 -0
@@ -0,0 +1,197 @@
1
+ # Changelog
2
+
3
+ Notable changes per release. Versions before 0.5.0 are recorded in the git history and the `v*` tags.
4
+
5
+ ## 0.6.0 — 2026-10-05
6
+
7
+ SQL Ripples, clustering, Pond environments and a simpler data plane. The Iceberg plane is gone: every
8
+ Catchment now publishes versioned Parquet, which pins each run's Source reads and costs a third of the
9
+ export time. See "Upgrading" at the end.
10
+
11
+ ### Building Ponds
12
+
13
+ - **SQL Ripples**: a Ripple can be one `SELECT` in a file, declared in `pond.toml` under
14
+ `[ripples.NAME]` with its `parents`, the Source tables it `reads`, and how it writes (`overwrite`,
15
+ `merge` or `append`). Nothing is inferred from the SQL; each query is checked against its declaration
16
+ at deploy, so a forgotten parent is refused instead of silently reading the previous run's table. SQL
17
+ and Python Ripples mix freely (`@ripple(parents=...)` now takes names), and column lineage is
18
+ recorded for SQL Ripples too. `duckstring pond demo --sql` shows the default pipeline in SQL.
19
+ - **Static tables**: files shipped with a Pond's code, declared under `[static.NAME]`, are read-only
20
+ tables every Ripple can query.
21
+ - **Pond environments**: a Pond with a `pyproject.toml` and `uv.lock` runs in its own environment, built
22
+ at deploy on the Catchment and by cloud and Pool Ducks on their own machines. A missing or stale lock,
23
+ or code that won't import, now refuses the deploy with the reason (an import error used to register no
24
+ Ripples).
25
+ - **`.pondignore`**: chooses which files a deploy uploads; by default `puddles/`, `.env` files, hidden
26
+ directories and caches stay behind.
27
+ - Names containing dots can be referenced with backticks (``sales.`daily.v2` ``).
28
+ - A merge Trickle's name is a view over its current state, so plain SQL in later Ripples reads what
29
+ `read_table` returns.
30
+
31
+ ### Performance
32
+
33
+ - **Clustering**: `cluster_by` on `merge_table`, the builder's `.merge()`, `write_table` and SQL Ripples
34
+ orders a table for reads that filter on those columns. One column sorts; two or more are interleaved
35
+ with a rank-Morton order (each column's quantile ranks, bit-interleaved), so every listed column
36
+ prunes, skewed ones included. A merge table's compacted base is now ordered by its primary key by
37
+ default instead of by freshness, which no read could use.
38
+ - A merge table's warm tier is read where it's published instead of being copied into a fresh Duck's
39
+ database, and is no longer also re-published in full on every export.
40
+
41
+ ### Data plane
42
+
43
+ - **Versioned overwrite tables**: each run publishes a plain table as a new immutable version, and a
44
+ Sink run reads the version its Source had published when the run started, even if the Source publishes
45
+ again meanwhile. Superseded versions are kept only while a running Sink still reads them.
46
+ - **The Iceberg plane is removed**, with `pyiceberg`, `DUCKSTRING_DATA_PLANE` and
47
+ `DUCKSTRING_ICEBERG_KEEP_SNAPSHOTS`. It cost about three times the export time, more than twice the
48
+ memory and over three times the disk of the Parquet plane, for interop no external engine could use.
49
+ - A registry DuckDB can't open (written by a newer DuckDB, or corrupt) is set aside and rebuilt from
50
+ published state instead of stopping the Duck.
51
+
52
+ ### Cloud and egress
53
+
54
+ - Flock settings and engine credentials travel with each run, so Fargate and EC2 Ducks can use the
55
+ Flock; they received neither before.
56
+ - The **Duck image is published** to GHCR for amd64 and arm64, tagged with the version, so cloud Ducks
57
+ need no image of your own unless Ponds need system libraries.
58
+ - The S3 Spout now honours `?endpoint=` and `DUCKSTRING_S3_ENDPOINT`, so it works with MinIO, R2 and
59
+ Ceph; it used to reach only AWS. Tested against MinIO in CI.
60
+ - A failed Spout delivery is retried when its source next publishes, and a Spout or alert destination
61
+ can be a single `${env:...}` or `${secret:...}` reference.
62
+ - A Pond Draw records its upstream's real version, so a `[sources]` pin above `{major}.0.0` deploys.
63
+ - A Duck that lost its local publish restores it from the durable layer before rebuilding, so Ponds on
64
+ the same machine never read it incomplete; the durable copy no longer loses warm bands after such a
65
+ rebuild.
66
+
67
+ ### Correctness and operability
68
+
69
+ - **DuckDB is bounded** to the tested range, `>=1.5,<2`, and CI tests the floor and the DuckDB 2.0
70
+ pre-release. A change to or from DuckDB 2.0's `VARIANT` type counts as breaking in the version
71
+ contract.
72
+ - `catchment init` and `start` warn when an open Catchment binds beyond loopback, since a deploy runs code
73
+ on the machine.
74
+ - Querying a missing table says the Pond hasn't run yet, or lists the tables it has.
75
+ - A Pool agent stopped with SIGTERM stops its Ducks.
76
+ - A Fargate or EC2 Duck shut down for being idle is replaced on the Pond's next run. Before, the Catchment
77
+ kept the departed Duck's record, launched nothing, and failed the run as "Lost contact" three minutes
78
+ later.
79
+ - An EC2 Duck's instance terminates when the Duck exits cleanly, instead of running, and billing, after
80
+ the Duck has gone. A Duck that crashes leaves its instance up so its console output can be read.
81
+ - SQL Ripples that read a Source work on the DuckDB 2.0 pre-release.
82
+ - A Catchment without API keys could answer a request with 401 under load. The Catchment's database
83
+ connection is shared by its threads, and the key check read it without the lock the engine holds,
84
+ so it sometimes saw a key that wasn't there. Every user of the connection now takes that lock, and
85
+ the key check is cached.
86
+ - A Duck that has heard nothing from its Catchment for an hour while idle exits, so a Duck left behind by
87
+ a Catchment that was deleted or stopped hard doesn't run, and bill, indefinitely. Set
88
+ `DUCKSTRING_DUCK_ORPHAN_MINUTES` on the Catchment to change the limit, or `0` to turn it off. Pool
89
+ machines aren't affected.
90
+
91
+ ### Documentation
92
+
93
+ The documentation is rewritten: Quickstart, Concepts, task-led Guides and a full Reference (Python API,
94
+ `pond.toml`, every CLI command group, the HTTP API, formats and environment variables), with the README and
95
+ landing page repositioned around Duckstring as a data engineering platform for DuckDB.
96
+
97
+ ### Upgrading
98
+
99
+ - **Published layout**: plain tables are now written as `{table}__v/{version}.parquet`. Readers still read
100
+ a Source's old single `{table}.parquet` until it publishes again, so Ponds can be upgraded in any order.
101
+ - **Leftover Iceberg files**: a data directory published by the Iceberg plane keeps a `catalog.json` and a
102
+ `pond/` directory that nothing reads now. Delete them; on a Catchment using local-first publish with an
103
+ S3 data root, the Persist mirror would otherwise upload them.
104
+ - **`DUCKSTRING_DATA_PLANE`** is no longer read; remove it from the environment.
105
+ - **DuckDB 2.0** isn't supported yet: installing Duckstring keeps DuckDB below 2, including in Pond
106
+ environments that lock a newer one.
107
+ - The Catchment database migrates itself on start (one new column).
108
+
109
+ ## 0.5.0 — 2026-08-03
110
+
111
+ The cloud release: a Catchment can now run its Ducks on AWS, keep its data on S3, and serve that data
112
+ over standard wire protocols. Everything below is opt-in — a Catchment with no cloud configuration
113
+ behaves exactly as it did in 0.4.0, and a `pond.toml` asking for remote compute still runs anywhere.
114
+
115
+ ### Cloud compute
116
+
117
+ - **Duck launchers**: a Pond's Duck runs on the Catchment's own box (default), on **Fargate** (the
118
+ default remote backend — serverless containers, task-role IAM), or on **EC2** (the escape hatch, for
119
+ sizes and GPUs Fargate does not offer). Built-in **S/M/L/XL preset pools** resolve with zero setup.
120
+ - **Duck Pools**: named remote-compute pools (`duckstring duck pool add`), with per-Pond compute
121
+ declared in `pond.toml` and overridable operationally (`duckstring duck set`).
122
+ - **The Pool agent**: one shared machine per named Pool hosting many co-resident Ducks.
123
+ - **Auto-relay**: a Catchment on a laptop behind NAT can run cloud Ducks with no manual tunnel — the
124
+ first remote spawn provisions a small always-reachable box and holds a reverse tunnel to it.
125
+ - **Remote failures are observable**: boot output is teed to the console (EC2) / CloudWatch (Fargate)
126
+ and the tail is attached to the Pond's failure, so a Duck that dies before dialling back still says
127
+ why. Spawn-failure reasons (missing image, missing AMI, bad IAM) surface on the Pond rather than as a
128
+ generic crash.
129
+ - Per-provider **startup grace** so a cold machine is not judged by the steady-state silence window.
130
+
131
+ ### Data plane on S3
132
+
133
+ - A Catchment's data root can be an object store (`duckstring catchment settings --data-root s3://…`),
134
+ with an S3-compatible **endpoint override** for MinIO/Ceph/R2.
135
+ - **Switch, adopt, or migrate** an existing plane, with a background copy and live progress.
136
+ - **Local-first publish + async Persist**: a run publishes locally and mirrors durably in the
137
+ background, so compute never waits on the object store; `persisted_f` is the durable watermark and
138
+ freshness gating is Pool-aware.
139
+ - **S3-resident state**: a merge base is read as a view rather than hydrated, and retention pruning is
140
+ floor-anchored rather than inferred from absence.
141
+
142
+ ### The Flock — over-envelope compute
143
+
144
+ A comprehensive `pond.trickle(...)` recompute that exceeds the Duck's memory envelope can be dispatched
145
+ to a serverless engine (**Athena** ships first), while the Duck keeps merge/diff/publish.
146
+
147
+ **DuckDB is the authority.** Dispatch decides *where* work runs, never *what* is published, enforced
148
+ structurally: an engine-owned allow-list of expressions proven equivalent on both engines (division and
149
+ CAST are excluded — the two engines genuinely disagree), a `conform` step that casts the engine's result
150
+ to DuckDB's own schema and rejects a differing column set, and degradation to local compute on any
151
+ failure. Dispatch counters ride `/metrics`, since a silently degrading Flock is otherwise invisible.
152
+
153
+ ### Data serving
154
+
155
+ - A sandboxed, warm, read-only query surface over published data: catalog = catchment, schema = pond.
156
+ - **Postgres wire** and **Arrow Flight SQL** adapters, plus `/api/serve` and a CLI.
157
+ - A **Catalog UI** unifying the query surface, with role-governed access.
158
+
159
+ ### Lineage
160
+
161
+ - Table-level (observed per run), column-level (static per version, with a sqlglot upgrade for SQL
162
+ outputs), and row-level temporal provenance (`duckstring trace`).
163
+ - **OpenLineage** events emitted on run completion for catalog integration.
164
+
165
+ ### dbt-mode Ponds
166
+
167
+ Deploy a dbt project as a Pond with no `@ripple` code: each dbt model becomes a Ripple with its own
168
+ freshness, failure and retry tracking, and `ref()` becomes the intra-Pond graph. Opt in with the
169
+ `duckstring[dbt]` extra.
170
+
171
+ ### Correctness and operability
172
+
173
+ - **Version contract**: a lossless widening of a column type is accepted (a data-dependent branch could
174
+ legitimately wedge a live Pond permanently); `reset-contract` is the escape hatch for a genuine
175
+ narrowing; contract failures carry a dedicated sub-reason end-to-end.
176
+ - **Reads reject a stale local publish** the Catchment knows is behind, rather than serving it.
177
+ - Pond Runs the Pond has moved past are closed rather than stranded at `running`.
178
+ - A Pool machine that disappears is detected and relaunched, instead of wedging every Pond on it.
179
+ - The **serving sandbox** no longer spills into the shared system temp — DuckDB exempts its temp
180
+ directory from the external-access lock, which on Linux exposed everything under `/tmp` to a
181
+ read-level user.
182
+ - An idle Duck backs off instead of polling ten times a second.
183
+ - Opt-in **re-notify cadence** for alert channels while a failure or freshness episode persists.
184
+ - Incremental object-store egress (`mode=append`) mirrors the published collection per delivery.
185
+
186
+ ### Testing
187
+
188
+ - The object-store data plane runs against a **real S3 API** in CI (MinIO, which rejects unsigned
189
+ requests — the class of bug moto cannot catch).
190
+ - Real-data demo sets: **TPC-DS** and **GHArchive** (the latter offline against a committed fixture).
191
+ - Python matrix, the dbt extra, real-Postgres egress, and frontend tests in CI.
192
+
193
+ ### Documentation
194
+
195
+ - A full **AWS guide** (`guides/cloud.md`): the cloud-enable gate, the three IAM roles, security-group
196
+ asymmetry, the AMI Python constraint, the build-your-own-image policy, and the Flock's authority rules.
197
+ - New guides for lineage, dbt-mode, and querying data.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: duckstring
3
- Version: 0.5.0
4
- Summary: Build data pipelines the way you build software: version each transform, declare its dependencies, and Duckstring resolves the execution DAG automatically.
3
+ Version: 0.6.0
4
+ Summary: An open source data engineering platform for DuckDB: versioned transformations, pull orchestration, incremental processing and a built-in catalog.
5
5
  Author-email: Duckstring <dev@duckstring.com>
6
6
  License: Apache License
7
7
  Version 2.0, January 2004
@@ -212,7 +212,7 @@ Project-URL: Playground, https://playground.duckstring.com
212
212
  Requires-Python: >=3.10
213
213
  Description-Content-Type: text/markdown
214
214
  License-File: LICENSE
215
- Requires-Dist: duckdb>=0.10
215
+ Requires-Dist: duckdb<2,>=1.5
216
216
  Requires-Dist: rich>=13.7
217
217
  Requires-Dist: typer>=0.12
218
218
  Requires-Dist: fastapi>=0.111
@@ -221,10 +221,10 @@ Requires-Dist: uvicorn[standard]>=0.29
221
221
  Requires-Dist: httpx>=0.25
222
222
  Requires-Dist: tomli>=2.0; python_version < "3.11"
223
223
  Requires-Dist: tomli-w>=1.0
224
- Requires-Dist: pyiceberg>=0.7
225
224
  Requires-Dist: pyarrow>=14
226
225
  Requires-Dist: pytz
227
- Provides-Extra: iceberg
226
+ Requires-Dist: pathspec>=0.10
227
+ Requires-Dist: uv>=0.5
228
228
  Provides-Extra: dbt
229
229
  Requires-Dist: dbt-duckdb>=1.9; extra == "dbt"
230
230
  Provides-Extra: lineage
@@ -247,85 +247,111 @@ Dynamic: license-file
247
247
 
248
248
  # Duckstring
249
249
 
250
- Build data pipelines the way you build software: version each transform, declare its dependencies, and Duckstring resolves the execution DAG automatically.
250
+ **Get your ducks in a row.**
251
251
 
252
- Duckstring treats data transformations as software packages. Upstream dependencies are declared per Pond (unit operation), defining the DAG without the need for its direct management.
252
+ Duckstring is an open source data engineering platform built on DuckDB. Data into the terabytes fits comfortably on a single machine, which is where DuckDB is at its best, and Duckstring provides what a pipeline needs around it: packaging and versioning, orchestration, incremental processing, a catalog and delivery. It runs the same on a laptop, on a single server, or with work spread across cloud machines.
253
253
 
254
- Ponds are upgraded and deployed to Duckstring's pull-based Catchment (orchestrator) atomically - like upgrading a package - with the earlier version continuing to execute until there are no consumers dependent on it. Upstream defines constraints on what it can consume, downstream defines when it's needed, and the Catchment optimally executes the sequence of Ponds supplying it with the best currency and frequency as possible.
254
+ [Documentation](https://docs.duckstring.com) · [Quickstart](https://docs.duckstring.com/quickstart) · [Playground](https://playground.duckstring.com)
255
255
 
256
- You should not need to manage the DAG. You should not need global governance. You should know yourself and your suppliers and trust that you'll get what you need when you need it.
256
+ ## Versioned transformations
257
257
 
258
- ## Core Concepts
258
+ Each set of transformations is a *Pond*: a Python project whose `pond.toml` names it, gives it a semantic version and lists the Ponds it reads from.
259
259
 
260
- The main elements:
260
+ ```toml
261
+ [pond]
262
+ name = "sales"
263
+ version = "1.2.0"
261
264
 
262
- - **Catchment**: Control environment (FastAPI + UI + CLI)
263
- - **Pond**: Versioned container with declared upstream dependencies
264
- - **Ripple**: Unit operation within a Pond (e.g. a single transformation producing a table)
265
- - **Trickle**: DBSP-based incremental engine operating within a Ripple
265
+ [sources]
266
+ transactions = "1.0.0"
267
+ products = "1.1.0"
268
+ ```
269
+
270
+ The pipeline follows from those declarations, so it never has to be drawn by hand. A breaking change ships as a new major version, which runs alongside the old one while downstream Ponds move across at their own pace.
271
+
272
+ ## Pull orchestration
266
273
 
267
- Ponds are typed or referred to in context:
274
+ Schedules are set on the Ponds whose output is actually used, at the end of the pipeline. Duckstring works back from there, running each upstream Pond only as often as something downstream needs it, so a path nobody consumes never runs. A daily job is one command:
275
+
276
+ ```bash
277
+ duckstring trigger tide reports 1d
278
+ ```
268
279
 
269
- - **Source**: A parent Pond
270
- - **Sink**: A child Pond
271
- - **Inlet**: A Pond with external dependencies and no Sources
272
- - **Outlet**: A Pond with no Sinks (e.g. outputs final data products)
280
+ ## Incremental processing
281
+
282
+ A *Trickle* stores a table's changes as a Z-set: rows weighted +1 when added and -1 when removed. Joins and aggregations written with the Trickle builder recompute only the keys that changed since the last run, and handle updates and deletes correctly across any shape of join:
283
+
284
+ ```python
285
+ from duckstring import ripple
286
+
287
+
288
+ @ripple
289
+ def priced_line(pond):
290
+ (
291
+ pond.trickle("orders.order_line")
292
+ .join(pond.trickle("catalog.product"), on="product_id")
293
+ .select(
294
+ "s0.order_id, s0.product_id, s0.quantity, s1.unit_price, "
295
+ "CAST(round(s0.quantity * s1.unit_price, 2) AS DECIMAL(14,2)) AS revenue"
296
+ )
297
+ .merge("priced_line", pk="order_id")
298
+ )
299
+ ```
273
300
 
274
- To see the orchestration model in action without installing anything, try the [Duckstring Playground](https://playground.duckstring.com).
301
+ ## Code and data together
302
+
303
+ The runtime, called the *Catchment*, is also the catalog. Each major version of a Pond is a schema holding its tables, with lineage, run history and the version's schema contract recorded alongside. Query it from the CLI, from Python, or from any Postgres client:
304
+
305
+ ```sql
306
+ SELECT * FROM reports_v1.monthly_summary;
307
+ ```
308
+
309
+ ## Python and the command line
310
+
311
+ Ripples, the steps inside a Pond, are Python functions with a DuckDB connection, so SQL, DuckDB's relation API, pandas and Arrow all work. Everything a Catchment does can be driven from the `duckstring` CLI or its HTTP API, and a web UI shows pipelines as they run.
275
312
 
276
313
  ## Quickstart
277
314
 
315
+ Duckstring needs Python 3.10 or newer.
316
+
278
317
  ```bash
279
318
  pip install duckstring
280
-
281
- # Start a local Catchment (the runtime + web UI) — leave it running
282
319
  duckstring catchment init --name dev
283
320
  ```
284
321
 
285
- Then, in another terminal:
322
+ This starts a local Catchment, with its web UI at http://127.0.0.1:7474. Leave it running, and in a second terminal:
286
323
 
287
324
  ```bash
288
- # Create the demo pipeline (transactions, products → sales → reports) and deploy it
289
325
  mkdir demo && cd demo
290
- duckstring pond demo
291
- duckstring pond deploy --all -y
292
-
293
- # Run it end to end, once
294
- duckstring trigger pulse reports
295
-
296
- # Look around
297
- duckstring status # live state of every Pond
298
- duckstring query reports monthly_summary # peek at an output table
326
+ duckstring pond demo # transactions, products → sales → reports
327
+ duckstring pond deploy --all --yes
328
+ duckstring trigger pulse reports # run the pipeline once, from the end
329
+ duckstring query reports monthly_summary
299
330
  ```
300
331
 
301
- The Catchment also serves a live web UI at `http://127.0.0.1:7474` — the Pond graph, freshness, run history, and the full trigger/control surface.
302
-
303
- ## Execution
332
+ The [Quickstart](https://docs.duckstring.com/quickstart) goes through the same steps with explanations.
304
333
 
305
- Ponds execute on demand signals sent to an Outlet, in two flavours — **push** runs the lineage forward to a target freshness; **pull** propagates demand upstream so every Pond re-runs as its Sources update, naturally throttled to the bottleneck. Each comes as a one-shot or a standing trigger:
334
+ ## Also included
306
335
 
307
- | | Once | Continuously |
308
- |---|---|---|
309
- | **Push** | Pulse | Tide |
310
- | **Pull** | Tap | Wave |
336
+ - [Local testing](https://docs.duckstring.com/guides/testing_with_puddles) against small samples of each Source, called Puddles
337
+ - [dbt projects](https://docs.duckstring.com/guides/dbt_projects) deployed as Ponds, one step per model
338
+ - [Delivery](https://docs.duckstring.com/guides/delivering_with_spouts) to Postgres (incremental and exactly once), S3, Google Cloud Storage and local files
339
+ - [Retries, alerts and Prometheus metrics](https://docs.duckstring.com/guides/monitoring_and_failures)
340
+ - [Per-Pond Python environments](https://docs.duckstring.com/guides/writing_ripples#python-dependencies) from `pyproject.toml` and `uv.lock`
341
+ - [Cloud compute](https://docs.duckstring.com/guides/cloud_compute_on_aws) on AWS Fargate or EC2, chosen per Pond, with data on S3
342
+ - [Connected Catchments](https://docs.duckstring.com/guides/connecting_catchments), where a Pond reads from a Pond on another Catchment
311
343
 
312
- A Tide keeps an Outlet no staler than a bound (`duckstring trigger tide reports 1d`); a Wave keeps it as fresh as the pipeline can supply. See [Triggers](https://docs.duckstring.com/guides/triggers) for the full semantics.
344
+ ## Documentation
313
345
 
314
- *There is no DAG.*
346
+ [docs.duckstring.com](https://docs.duckstring.com) has the concepts, task guides, and a reference for the CLI, Python API, `pond.toml` and HTTP API. To try pull orchestration without installing anything, use the [Playground](https://playground.duckstring.com).
315
347
 
316
- ## Going further
348
+ ## Status
317
349
 
318
- Full documentation lives at **[docs.duckstring.com](https://docs.duckstring.com)**:
350
+ Duckstring is pre-1.0, so interfaces may still change between minor versions. Changes are listed in the [changelog](CHANGELOG.md).
319
351
 
320
- - [Quickstart](https://docs.duckstring.com/getting-started/quickstart) — the path above, with explanations
321
- - [Theory](https://docs.duckstring.com/theory) — the freshness-based orchestration model in depth
322
- - [Versioning](https://docs.duckstring.com/concepts/versioning) — SemVer on Ponds, concurrent major versions, atomic upgrades
323
- - [Local testing](https://docs.duckstring.com/guides/local-testing) — Puddles: test a Pond before deploying it
324
- - [Fault tolerance](https://docs.duckstring.com/guides/fault-tolerance) — retry budgets, failure states, recovery
325
- - [Running a Catchment](https://docs.duckstring.com/guides/running-a-catchment) — hosting, authentication, platform deployment
326
- - [CLI](https://docs.duckstring.com/reference/cli) / [HTTP API](https://docs.duckstring.com/reference/http-api) — full references
352
+ ## Hosted Catchments
327
353
 
328
- There are future plans for a hosted Catchment service at [duckstring.com](https://duckstring.com). If you're interested, please [get in touch](mailto:dev@duckstring.com).
354
+ A hosted Catchment service is planned at [duckstring.com](https://duckstring.com). If you're interested, get in touch at [dev@duckstring.com](mailto:dev@duckstring.com).
329
355
 
330
356
  ## License
331
357
 
@@ -0,0 +1,111 @@
1
+ # Duckstring
2
+
3
+ **Get your ducks in a row.**
4
+
5
+ Duckstring is an open source data engineering platform built on DuckDB. Data into the terabytes fits comfortably on a single machine, which is where DuckDB is at its best, and Duckstring provides what a pipeline needs around it: packaging and versioning, orchestration, incremental processing, a catalog and delivery. It runs the same on a laptop, on a single server, or with work spread across cloud machines.
6
+
7
+ [Documentation](https://docs.duckstring.com) · [Quickstart](https://docs.duckstring.com/quickstart) · [Playground](https://playground.duckstring.com)
8
+
9
+ ## Versioned transformations
10
+
11
+ Each set of transformations is a *Pond*: a Python project whose `pond.toml` names it, gives it a semantic version and lists the Ponds it reads from.
12
+
13
+ ```toml
14
+ [pond]
15
+ name = "sales"
16
+ version = "1.2.0"
17
+
18
+ [sources]
19
+ transactions = "1.0.0"
20
+ products = "1.1.0"
21
+ ```
22
+
23
+ The pipeline follows from those declarations, so it never has to be drawn by hand. A breaking change ships as a new major version, which runs alongside the old one while downstream Ponds move across at their own pace.
24
+
25
+ ## Pull orchestration
26
+
27
+ Schedules are set on the Ponds whose output is actually used, at the end of the pipeline. Duckstring works back from there, running each upstream Pond only as often as something downstream needs it, so a path nobody consumes never runs. A daily job is one command:
28
+
29
+ ```bash
30
+ duckstring trigger tide reports 1d
31
+ ```
32
+
33
+ ## Incremental processing
34
+
35
+ A *Trickle* stores a table's changes as a Z-set: rows weighted +1 when added and -1 when removed. Joins and aggregations written with the Trickle builder recompute only the keys that changed since the last run, and handle updates and deletes correctly across any shape of join:
36
+
37
+ ```python
38
+ from duckstring import ripple
39
+
40
+
41
+ @ripple
42
+ def priced_line(pond):
43
+ (
44
+ pond.trickle("orders.order_line")
45
+ .join(pond.trickle("catalog.product"), on="product_id")
46
+ .select(
47
+ "s0.order_id, s0.product_id, s0.quantity, s1.unit_price, "
48
+ "CAST(round(s0.quantity * s1.unit_price, 2) AS DECIMAL(14,2)) AS revenue"
49
+ )
50
+ .merge("priced_line", pk="order_id")
51
+ )
52
+ ```
53
+
54
+ ## Code and data together
55
+
56
+ The runtime, called the *Catchment*, is also the catalog. Each major version of a Pond is a schema holding its tables, with lineage, run history and the version's schema contract recorded alongside. Query it from the CLI, from Python, or from any Postgres client:
57
+
58
+ ```sql
59
+ SELECT * FROM reports_v1.monthly_summary;
60
+ ```
61
+
62
+ ## Python and the command line
63
+
64
+ Ripples, the steps inside a Pond, are Python functions with a DuckDB connection, so SQL, DuckDB's relation API, pandas and Arrow all work. Everything a Catchment does can be driven from the `duckstring` CLI or its HTTP API, and a web UI shows pipelines as they run.
65
+
66
+ ## Quickstart
67
+
68
+ Duckstring needs Python 3.10 or newer.
69
+
70
+ ```bash
71
+ pip install duckstring
72
+ duckstring catchment init --name dev
73
+ ```
74
+
75
+ This starts a local Catchment, with its web UI at http://127.0.0.1:7474. Leave it running, and in a second terminal:
76
+
77
+ ```bash
78
+ mkdir demo && cd demo
79
+ duckstring pond demo # transactions, products → sales → reports
80
+ duckstring pond deploy --all --yes
81
+ duckstring trigger pulse reports # run the pipeline once, from the end
82
+ duckstring query reports monthly_summary
83
+ ```
84
+
85
+ The [Quickstart](https://docs.duckstring.com/quickstart) goes through the same steps with explanations.
86
+
87
+ ## Also included
88
+
89
+ - [Local testing](https://docs.duckstring.com/guides/testing_with_puddles) against small samples of each Source, called Puddles
90
+ - [dbt projects](https://docs.duckstring.com/guides/dbt_projects) deployed as Ponds, one step per model
91
+ - [Delivery](https://docs.duckstring.com/guides/delivering_with_spouts) to Postgres (incremental and exactly once), S3, Google Cloud Storage and local files
92
+ - [Retries, alerts and Prometheus metrics](https://docs.duckstring.com/guides/monitoring_and_failures)
93
+ - [Per-Pond Python environments](https://docs.duckstring.com/guides/writing_ripples#python-dependencies) from `pyproject.toml` and `uv.lock`
94
+ - [Cloud compute](https://docs.duckstring.com/guides/cloud_compute_on_aws) on AWS Fargate or EC2, chosen per Pond, with data on S3
95
+ - [Connected Catchments](https://docs.duckstring.com/guides/connecting_catchments), where a Pond reads from a Pond on another Catchment
96
+
97
+ ## Documentation
98
+
99
+ [docs.duckstring.com](https://docs.duckstring.com) has the concepts, task guides, and a reference for the CLI, Python API, `pond.toml` and HTTP API. To try pull orchestration without installing anything, use the [Playground](https://playground.duckstring.com).
100
+
101
+ ## Status
102
+
103
+ Duckstring is pre-1.0, so interfaces may still change between minor versions. Changes are listed in the [changelog](CHANGELOG.md).
104
+
105
+ ## Hosted Catchments
106
+
107
+ A hosted Catchment service is planned at [duckstring.com](https://duckstring.com). If you're interested, get in touch at [dev@duckstring.com](mailto:dev@duckstring.com).
108
+
109
+ ## License
110
+
111
+ [Apache 2.0](LICENSE)
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "duckstring"
3
- version = "0.5.0"
4
- description = "Build data pipelines the way you build software: version each transform, declare its dependencies, and Duckstring resolves the execution DAG automatically."
3
+ version = "0.6.0"
4
+ description = "An open source data engineering platform for DuckDB: versioned transformations, pull orchestration, incremental processing and a built-in catalog."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  license = { file = "LICENSE" }
@@ -10,7 +10,10 @@ authors = [
10
10
  ]
11
11
 
12
12
  dependencies = [
13
- "duckdb>=0.10",
13
+ # The tested range: CI runs the suite on the floor and the latest release, and against the pre-release
14
+ # in an allowed-to-fail job. Raise the ceiling once the suite passes on the next major's release
15
+ # (plans/duckdb-2-ready.md). A Pond environment gets this Duckstring, so these bounds hold there too.
16
+ "duckdb>=1.5,<2",
14
17
  "rich>=13.7",
15
18
  "typer>=0.12",
16
19
  "fastapi>=0.111",
@@ -19,22 +22,20 @@ dependencies = [
19
22
  "httpx>=0.25",
20
23
  "tomli>=2.0; python_version < '3.11'",
21
24
  "tomli-w>=1.0",
22
- # The default data plane: an Apache Iceberg base layer (snapshots + schema metadata over the
23
- # Parquet files we already write). Core deps because Iceberg is default-on; set
24
- # DUCKSTRING_DATA_PLANE=parquet to opt out (the lighter fallback, also for offline Catchments that
25
- # can't fetch DuckDB's iceberg extension). NB: we use our own file-backed catalog
26
- # (duckstring.iceberg_catalog) rather than pyiceberg's SqlCatalog, so SQLAlchemy is NOT pulled in.
27
- "pyiceberg>=0.7",
25
+ # Arrow interchange: the Flight SQL server and Arrow fetches from DuckDB.
28
26
  "pyarrow>=14",
29
27
  # DuckDB requires pytz to convert TIMESTAMPTZ columns to Python datetimes on fetch
30
28
  # (our _duckstring_f freshness column is TIMESTAMPTZ, read back in dataplane/trickle export).
31
29
  # It is otherwise only present transitively (pandas), so a clean install lacks it.
32
30
  "pytz",
31
+ # .pondignore matching with exact .gitignore semantics (negation, anchoring, **). Pure Python.
32
+ "pathspec>=0.10",
33
+ # Builds each Pond's own environment from its pyproject.toml + uv.lock (plans/pond-environments.md).
34
+ # A self-contained binary wheel; duckstring.environments finds it with uv.find_uv_bin().
35
+ "uv>=0.5",
33
36
  ]
34
37
 
35
38
  [project.optional-dependencies]
36
- # Retained for back-compat: the Iceberg deps are in core now (Iceberg is the default plane).
37
- iceberg = []
38
39
  # dbt-mode Ponds (plans/dbt.md): deploy a dbt project as a Pond, each model a Ripple. Heavyweight and
39
40
  # opt-in — only pulled when a team actually runs dbt-mode. dbt-duckdb pulls dbt-core transitively.
40
41
  dbt = [
@@ -88,6 +89,9 @@ ds = "duckstring.cli:main"
88
89
 
89
90
  [tool.pytest.ini_options]
90
91
  testpaths = ["tests"]
92
+ # Some suites share fixtures by importing `tests.<module>`; put the repo root on the path so a bare
93
+ # `pytest` (what CI runs) resolves them the same as `python -m pytest`.
94
+ pythonpath = ["."]
91
95
  # Tight enough to catch an accidental real sleep (sim tests finish in ms), loose enough to absorb
92
96
  # cold-runner I/O on CI (SQLite migrate / uvicorn startup can exceed 1s on shared runners).
93
97
  timeout = 5
@@ -27,19 +27,37 @@ class NotifierError(ValueError):
27
27
 
28
28
  @dataclass(frozen=True)
29
29
  class Destination:
30
- scheme: str
30
+ scheme: str | None # None: the whole destination is a reference, its scheme known only once resolved
31
31
  raw: str # the original URI, with ${...} references intact (resolved only at send time)
32
32
 
33
33
 
34
- def parse_notifier_destination(uri: str) -> Destination:
34
+ def parse_notifier_destination(uri: str, *, resolve: bool = False) -> Destination:
35
35
  """Validate a channel destination URI: a known scheme + well-formed ``${...}`` references. Does **not**
36
- resolve credentials (so a channel can be created before its secrets are present). Raises NotifierError."""
36
+ resolve credentials (so a channel can be created before its secrets are present). Raises NotifierError.
37
+
38
+ A destination that is **entirely** one reference (``${secret:SLACK_WEBHOOK}``) is accepted with
39
+ ``scheme=None``; ``resolve=True`` (at send/test time) resolves it and checks the resulting scheme. Errors
40
+ name the reference, never its value."""
37
41
  if not uri or not uri.strip():
38
42
  raise NotifierError("destination must not be empty")
39
43
  try:
40
44
  credentials.references(uri) # validates ${env:}/${secret:} syntax
41
45
  except credentials.CredentialError as exc:
42
46
  raise NotifierError(str(exc)) from exc
47
+ if credentials.whole_reference(uri):
48
+ if not resolve:
49
+ return Destination(scheme=None, raw=uri)
50
+ try:
51
+ target = credentials.resolve(uri)
52
+ except credentials.CredentialError as exc:
53
+ raise NotifierError(str(exc)) from exc
54
+ scheme = urlparse(target).scheme.lower()
55
+ if scheme not in KNOWN_SCHEMES:
56
+ raise NotifierError(
57
+ f"destination {uri.strip()} resolves to a value without a supported scheme — expected "
58
+ "https://…, http://… or mailto:…"
59
+ )
60
+ return Destination(scheme=scheme, raw=uri)
43
61
  scheme = urlparse(uri).scheme.lower()
44
62
  if not scheme:
45
63
  raise NotifierError(f"destination {uri!r} has no scheme — expected e.g. https://…, mailto:…")
@@ -72,7 +90,7 @@ def register(scheme: str, factory: Callable[[Destination], Notifier]) -> None:
72
90
  def get_notifier(destination: str) -> Notifier:
73
91
  """Resolve the notifier for a channel destination by its scheme. Raises :class:`NotifierError` for an
74
92
  unknown scheme, or a known scheme whose notifier is not built."""
75
- dest = parse_notifier_destination(destination)
93
+ dest = parse_notifier_destination(destination, resolve=True)
76
94
  factory = _REGISTRY.get(dest.scheme)
77
95
  if factory is None:
78
96
  raise NotifierError(
@@ -23,7 +23,15 @@ class EmailNotifier:
23
23
  SCHEMES = ("mailto",)
24
24
 
25
25
  def __init__(self, dest: Destination):
26
- parsed = urlparse(dest.raw)
26
+ from ..egress import credentials
27
+
28
+ raw = dest.raw
29
+ if credentials.whole_reference(raw): # the whole mailto: URI is held in a variable or secret
30
+ try:
31
+ raw = credentials.resolve(raw)
32
+ except credentials.CredentialError as exc:
33
+ raise NotifierError(str(exc)) from exc
34
+ parsed = urlparse(raw)
27
35
  self.recipients = [r.strip() for r in unquote(parsed.path).split(",") if r.strip()]
28
36
  if not self.recipients:
29
37
  raise NotifierError("mailto: destination has no recipient — use mailto:you@example.com")
@@ -198,7 +198,7 @@ def create_app(
198
198
  data_root = get_setting(con, DATA_ROOT_KEY)
199
199
 
200
200
  # Writer lease on an external data root — refuse to start if a *different* live Catchment owns it (two
201
- # Catchments racing one lake's Iceberg catalog would dangle its pointer). A same-id restart reclaims
201
+ # Catchments publishing and pruning the same lines would break single-writer-per-line). A same-id restart reclaims
202
202
  # instantly; only engaged for an external DUCKSTRING_DATA_ROOT, so the local default is untouched.
203
203
  data_lease = None
204
204
  if data_root: