dblect 0.1.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 (334) hide show
  1. dblect-0.1.0/.github/workflows/ci.yml +65 -0
  2. dblect-0.1.0/.github/workflows/release.yml +47 -0
  3. dblect-0.1.0/.gitignore +225 -0
  4. dblect-0.1.0/CHANGELOG.md +134 -0
  5. dblect-0.1.0/CLAUDE.md +46 -0
  6. dblect-0.1.0/LICENSE +201 -0
  7. dblect-0.1.0/PKG-INFO +443 -0
  8. dblect-0.1.0/README.md +211 -0
  9. dblect-0.1.0/docs/README.md +31 -0
  10. dblect-0.1.0/docs/current_state/architecture.md +429 -0
  11. dblect-0.1.0/docs/current_state/capabilities.md +77 -0
  12. dblect-0.1.0/docs/current_state/incremental-worlds.md +53 -0
  13. dblect-0.1.0/docs/dblect-overview.md +29 -0
  14. dblect-0.1.0/docs/design/column-level-lineage.md +184 -0
  15. dblect-0.1.0/docs/design/config-and-flag-worlds.md +319 -0
  16. dblect-0.1.0/docs/design/contract-directed-generation.md +149 -0
  17. dblect-0.1.0/docs/design/dblect_technical_intro.md +651 -0
  18. dblect-0.1.0/docs/design/declaration-dsl.md +600 -0
  19. dblect-0.1.0/docs/design/demo_walkthrough.md +487 -0
  20. dblect-0.1.0/docs/design/design-concepts-digest.md +190 -0
  21. dblect-0.1.0/docs/design/domain-type-algebra.md +274 -0
  22. dblect-0.1.0/docs/design/domain-type-functions.md +90 -0
  23. dblect-0.1.0/docs/design/domain-type-maybe.md +107 -0
  24. dblect-0.1.0/docs/design/domain-type-substrate-readiness.md +45 -0
  25. dblect-0.1.0/docs/design/dsl-reference.md +251 -0
  26. dblect-0.1.0/docs/design/flags_and_configs_as_types.md +312 -0
  27. dblect-0.1.0/docs/design/hazard-algebra.md +187 -0
  28. dblect-0.1.0/docs/design/lineage-facts-types.md +361 -0
  29. dblect-0.1.0/docs/design/lineage-facts.md +284 -0
  30. dblect-0.1.0/docs/design/nullability-hazards.md +109 -0
  31. dblect-0.1.0/docs/design/propagation-soundness.md +109 -0
  32. dblect-0.1.0/docs/design/research/dbt-config-patterns.md +257 -0
  33. dblect-0.1.0/docs/design/research/variational-analysis-literature.md +72 -0
  34. dblect-0.1.0/docs/design/tiers_and_rough_implementation_order.md +329 -0
  35. dblect-0.1.0/docs/design/var-inference-spec.md +463 -0
  36. dblect-0.1.0/pyproject.toml +157 -0
  37. dblect-0.1.0/questions_and_decisions.md +449 -0
  38. dblect-0.1.0/scripts/refresh_bigquery_fixtures.sh +58 -0
  39. dblect-0.1.0/scripts/refresh_jaffle_fixtures.sh +58 -0
  40. dblect-0.1.0/scripts/refresh_scenarios.sh +84 -0
  41. dblect-0.1.0/scripts/refresh_snapshot_audit.sh +67 -0
  42. dblect-0.1.0/src/dblect/__init__.py +28 -0
  43. dblect-0.1.0/src/dblect/_version.py +1 -0
  44. dblect-0.1.0/src/dblect/adapters/__init__.py +40 -0
  45. dblect-0.1.0/src/dblect/adapters/builtin/__init__.py +7 -0
  46. dblect-0.1.0/src/dblect/adapters/builtin/bigquery.py +18 -0
  47. dblect-0.1.0/src/dblect/adapters/builtin/duckdb.py +24 -0
  48. dblect-0.1.0/src/dblect/adapters/builtin/postgres.py +17 -0
  49. dblect-0.1.0/src/dblect/adapters/builtin/redshift.py +17 -0
  50. dblect-0.1.0/src/dblect/adapters/builtin/snowflake.py +16 -0
  51. dblect-0.1.0/src/dblect/adapters/model.py +87 -0
  52. dblect-0.1.0/src/dblect/adapters/registry.py +108 -0
  53. dblect-0.1.0/src/dblect/analysis.py +125 -0
  54. dblect-0.1.0/src/dblect/audit/__init__.py +26 -0
  55. dblect-0.1.0/src/dblect/audit/sourcemap.py +177 -0
  56. dblect-0.1.0/src/dblect/audit/suppress.py +273 -0
  57. dblect-0.1.0/src/dblect/audit/walker.py +307 -0
  58. dblect-0.1.0/src/dblect/baseline.py +29 -0
  59. dblect-0.1.0/src/dblect/bootstrap/__init__.py +97 -0
  60. dblect-0.1.0/src/dblect/bootstrap/skill.md +290 -0
  61. dblect-0.1.0/src/dblect/check/__init__.py +59 -0
  62. dblect-0.1.0/src/dblect/check/coverage.py +132 -0
  63. dblect-0.1.0/src/dblect/check/findings.py +135 -0
  64. dblect-0.1.0/src/dblect/check/flags.py +79 -0
  65. dblect-0.1.0/src/dblect/check/incremental.py +122 -0
  66. dblect-0.1.0/src/dblect/check/run.py +713 -0
  67. dblect-0.1.0/src/dblect/check/worlds.py +126 -0
  68. dblect-0.1.0/src/dblect/cli/__init__.py +543 -0
  69. dblect-0.1.0/src/dblect/contracts/__init__.py +59 -0
  70. dblect-0.1.0/src/dblect/contracts/ast.py +213 -0
  71. dblect-0.1.0/src/dblect/contracts/compile.py +374 -0
  72. dblect-0.1.0/src/dblect/contracts/decorator.py +80 -0
  73. dblect-0.1.0/src/dblect/contracts/proxy.py +343 -0
  74. dblect-0.1.0/src/dblect/contracts/stubs.py +91 -0
  75. dblect-0.1.0/src/dblect/demo/__init__.py +20 -0
  76. dblect-0.1.0/src/dblect/demo/enums.py +65 -0
  77. dblect-0.1.0/src/dblect/demo/library.py +21 -0
  78. dblect-0.1.0/src/dblect/execution/__init__.py +5 -0
  79. dblect-0.1.0/src/dblect/execution/incremental.py +168 -0
  80. dblect-0.1.0/src/dblect/execution/project_env.py +69 -0
  81. dblect-0.1.0/src/dblect/execution/run.py +255 -0
  82. dblect-0.1.0/src/dblect/flatten/__init__.py +13 -0
  83. dblect-0.1.0/src/dblect/flatten/detector.py +69 -0
  84. dblect-0.1.0/src/dblect/lineage/__init__.py +41 -0
  85. dblect-0.1.0/src/dblect/lineage/builder.py +1123 -0
  86. dblect-0.1.0/src/dblect/lineage/facts/__init__.py +92 -0
  87. dblect-0.1.0/src/dblect/lineage/facts/grounding.py +190 -0
  88. dblect-0.1.0/src/dblect/lineage/facts/lattice.py +71 -0
  89. dblect-0.1.0/src/dblect/lineage/facts/model.py +170 -0
  90. dblect-0.1.0/src/dblect/lineage/facts/property.py +338 -0
  91. dblect-0.1.0/src/dblect/lineage/facts/registry.py +115 -0
  92. dblect-0.1.0/src/dblect/lineage/graph.py +262 -0
  93. dblect-0.1.0/src/dblect/lineage/predicate.py +521 -0
  94. dblect-0.1.0/src/dblect/lineage/properties/__init__.py +84 -0
  95. dblect-0.1.0/src/dblect/lineage/properties/activation.py +43 -0
  96. dblect-0.1.0/src/dblect/lineage/properties/aggregation_depth.py +71 -0
  97. dblect-0.1.0/src/dblect/lineage/properties/array_nonemptiness.py +207 -0
  98. dblect-0.1.0/src/dblect/lineage/properties/domain_type.py +709 -0
  99. dblect-0.1.0/src/dblect/lineage/properties/functional_dependency.py +540 -0
  100. dblect-0.1.0/src/dblect/lineage/properties/nullability.py +644 -0
  101. dblect-0.1.0/src/dblect/lineage/properties/predicate_flow.py +316 -0
  102. dblect-0.1.0/src/dblect/lineage/properties/uniqueness.py +1252 -0
  103. dblect-0.1.0/src/dblect/lineage/properties/where_provenance.py +57 -0
  104. dblect-0.1.0/src/dblect/lineage/property.py +466 -0
  105. dblect-0.1.0/src/dblect/lineage/semiring.py +110 -0
  106. dblect-0.1.0/src/dblect/loader.py +130 -0
  107. dblect-0.1.0/src/dblect/manifest/__init__.py +42 -0
  108. dblect-0.1.0/src/dblect/manifest/catalog.py +63 -0
  109. dblect-0.1.0/src/dblect/manifest/dag.py +155 -0
  110. dblect-0.1.0/src/dblect/manifest/parse.py +732 -0
  111. dblect-0.1.0/src/dblect/nullability/__init__.py +23 -0
  112. dblect-0.1.0/src/dblect/nullability/detector.py +502 -0
  113. dblect-0.1.0/src/dblect/py.typed +0 -0
  114. dblect-0.1.0/src/dblect/report.py +492 -0
  115. dblect-0.1.0/src/dblect/sarif.py +418 -0
  116. dblect-0.1.0/src/dblect/severity.py +152 -0
  117. dblect-0.1.0/src/dblect/snapshot/__init__.py +18 -0
  118. dblect-0.1.0/src/dblect/snapshot/detector.py +142 -0
  119. dblect-0.1.0/src/dblect/sql/__init__.py +101 -0
  120. dblect-0.1.0/src/dblect/sql/_sqlglot.py +447 -0
  121. dblect-0.1.0/src/dblect/sql/aggregates.py +181 -0
  122. dblect-0.1.0/src/dblect/sql/findings.py +100 -0
  123. dblect-0.1.0/src/dblect/sql/guards.py +261 -0
  124. dblect-0.1.0/src/dblect/sql/parse.py +166 -0
  125. dblect-0.1.0/src/dblect/sql/patterns.py +889 -0
  126. dblect-0.1.0/src/dblect/sql/vocab.py +240 -0
  127. dblect-0.1.0/src/dblect/templating.py +83 -0
  128. dblect-0.1.0/src/dblect/types/__init__.py +106 -0
  129. dblect-0.1.0/src/dblect/types/bridge.py +732 -0
  130. dblect-0.1.0/src/dblect/types/contract.py +325 -0
  131. dblect-0.1.0/src/dblect/types/domain.py +270 -0
  132. dblect-0.1.0/src/dblect/types/enums.py +28 -0
  133. dblect-0.1.0/src/dblect/types/errors.py +15 -0
  134. dblect-0.1.0/src/dblect/types/scalars.py +148 -0
  135. dblect-0.1.0/src/dblect/uniqueness/__init__.py +24 -0
  136. dblect-0.1.0/src/dblect/uniqueness/detector.py +1070 -0
  137. dblect-0.1.0/src/dblect/varinf/__init__.py +52 -0
  138. dblect-0.1.0/src/dblect/varinf/usage.py +224 -0
  139. dblect-0.1.0/src/dblect/varinf/walker.py +343 -0
  140. dblect-0.1.0/tests/__init__.py +0 -0
  141. dblect-0.1.0/tests/audit/__init__.py +0 -0
  142. dblect-0.1.0/tests/audit/test_fanout_fd_grounding.py +127 -0
  143. dblect-0.1.0/tests/audit/test_nullable_key_fd_grounding.py +92 -0
  144. dblect-0.1.0/tests/audit/test_sourcemap.py +224 -0
  145. dblect-0.1.0/tests/audit/test_suppress.py +432 -0
  146. dblect-0.1.0/tests/audit/test_walker.py +564 -0
  147. dblect-0.1.0/tests/bootstrap/__init__.py +0 -0
  148. dblect-0.1.0/tests/bootstrap/test_skill_drift.py +35 -0
  149. dblect-0.1.0/tests/check/__init__.py +0 -0
  150. dblect-0.1.0/tests/check/conftest.py +16 -0
  151. dblect-0.1.0/tests/check/test_compilation_coverage.py +105 -0
  152. dblect-0.1.0/tests/check/test_cross_world.py +88 -0
  153. dblect-0.1.0/tests/check/test_flags.py +126 -0
  154. dblect-0.1.0/tests/check/test_incremental_check.py +63 -0
  155. dblect-0.1.0/tests/check/test_run_check.py +799 -0
  156. dblect-0.1.0/tests/check/test_world_staging.py +97 -0
  157. dblect-0.1.0/tests/check/test_worlds.py +254 -0
  158. dblect-0.1.0/tests/cli/__init__.py +0 -0
  159. dblect-0.1.0/tests/cli/test_check.py +329 -0
  160. dblect-0.1.0/tests/cli/test_check_baseline.py +97 -0
  161. dblect-0.1.0/tests/cli/test_init_and_check.py +294 -0
  162. dblect-0.1.0/tests/cli/test_setup.py +81 -0
  163. dblect-0.1.0/tests/conftest.py +95 -0
  164. dblect-0.1.0/tests/contracts/__init__.py +0 -0
  165. dblect-0.1.0/tests/contracts/test_compile.py +183 -0
  166. dblect-0.1.0/tests/contracts/test_contract_capture.py +63 -0
  167. dblect-0.1.0/tests/contracts/test_pbt_contracts.py +96 -0
  168. dblect-0.1.0/tests/contracts/test_proxy_builds_ast.py +137 -0
  169. dblect-0.1.0/tests/contracts/test_stubs.py +99 -0
  170. dblect-0.1.0/tests/execution/__init__.py +0 -0
  171. dblect-0.1.0/tests/execution/test_incremental.py +81 -0
  172. dblect-0.1.0/tests/execution/test_run.py +114 -0
  173. dblect-0.1.0/tests/fixtures/incremental/dbt_project.yml +5 -0
  174. dblect-0.1.0/tests/fixtures/incremental/models/inc_stateful.sql +14 -0
  175. dblect-0.1.0/tests/fixtures/incremental/models/inc_watermark.sql +7 -0
  176. dblect-0.1.0/tests/fixtures/incremental/seeds/events.csv +3 -0
  177. dblect-0.1.0/tests/fixtures/incremental/seeds/schema.yml +20 -0
  178. dblect-0.1.0/tests/fixtures/incremental/seeds/state.csv +4 -0
  179. dblect-0.1.0/tests/fixtures/jaffle/manifest.json +1 -0
  180. dblect-0.1.0/tests/fixtures/jaffle_bigquery/manifest.json +1 -0
  181. dblect-0.1.0/tests/fixtures/jaffle_project/dbt_project.yml +34 -0
  182. dblect-0.1.0/tests/fixtures/jaffle_project/models/customers.sql +69 -0
  183. dblect-0.1.0/tests/fixtures/jaffle_project/models/docs.md +14 -0
  184. dblect-0.1.0/tests/fixtures/jaffle_project/models/orders.sql +56 -0
  185. dblect-0.1.0/tests/fixtures/jaffle_project/models/schema.yml +84 -0
  186. dblect-0.1.0/tests/fixtures/jaffle_project/models/staging/schema.yml +33 -0
  187. dblect-0.1.0/tests/fixtures/jaffle_project/models/staging/stg_customers.sql +22 -0
  188. dblect-0.1.0/tests/fixtures/jaffle_project/models/staging/stg_orders.sql +23 -0
  189. dblect-0.1.0/tests/fixtures/jaffle_project/models/staging/stg_payments.sql +25 -0
  190. dblect-0.1.0/tests/fixtures/jaffle_project/seeds/raw_customers.csv +101 -0
  191. dblect-0.1.0/tests/fixtures/jaffle_project/seeds/raw_orders.csv +100 -0
  192. dblect-0.1.0/tests/fixtures/jaffle_project/seeds/raw_payments.csv +114 -0
  193. dblect-0.1.0/tests/fixtures/jaffle_snowflake_meta/manifest.json +1 -0
  194. dblect-0.1.0/tests/fixtures/sarif/sarif-2.1.0.schema.json +2882 -0
  195. dblect-0.1.0/tests/fixtures/scenarios/README.md +86 -0
  196. dblect-0.1.0/tests/fixtures/scenarios/base/dbt_project.yml +21 -0
  197. dblect-0.1.0/tests/fixtures/scenarios/base/models/staging/schema.yml +50 -0
  198. dblect-0.1.0/tests/fixtures/scenarios/base/models/staging/stg_customers.sql +22 -0
  199. dblect-0.1.0/tests/fixtures/scenarios/base/models/staging/stg_orders.sql +23 -0
  200. dblect-0.1.0/tests/fixtures/scenarios/base/models/staging/stg_payments.sql +23 -0
  201. dblect-0.1.0/tests/fixtures/scenarios/base/seeds/raw_customers.csv +101 -0
  202. dblect-0.1.0/tests/fixtures/scenarios/base/seeds/raw_orders.csv +100 -0
  203. dblect-0.1.0/tests/fixtures/scenarios/base/seeds/raw_payments.csv +117 -0
  204. dblect-0.1.0/tests/fixtures/scenarios/cases/currency_creep/dblect/__init__.py +0 -0
  205. dblect-0.1.0/tests/fixtures/scenarios/cases/currency_creep/dblect/contracts.py +15 -0
  206. dblect-0.1.0/tests/fixtures/scenarios/cases/currency_creep/expected.yml +9 -0
  207. dblect-0.1.0/tests/fixtures/scenarios/cases/currency_creep/manifest.json +1 -0
  208. dblect-0.1.0/tests/fixtures/scenarios/cases/currency_creep/overlay/models/marts/order_revenue.sql +8 -0
  209. dblect-0.1.0/tests/fixtures/scenarios/cases/currency_creep/story.md +34 -0
  210. dblect-0.1.0/tests/fixtures/scenarios/cases/order_rollup_sound/dblect/__init__.py +0 -0
  211. dblect-0.1.0/tests/fixtures/scenarios/cases/order_rollup_sound/dblect/contracts.py +14 -0
  212. dblect-0.1.0/tests/fixtures/scenarios/cases/order_rollup_sound/expected.yml +3 -0
  213. dblect-0.1.0/tests/fixtures/scenarios/cases/order_rollup_sound/manifest.json +1 -0
  214. dblect-0.1.0/tests/fixtures/scenarios/cases/order_rollup_sound/overlay/models/marts/revenue_by_order.sql +9 -0
  215. dblect-0.1.0/tests/fixtures/scenarios/cases/order_rollup_sound/story.md +35 -0
  216. dblect-0.1.0/tests/fixtures/scenarios/cases/revenue_by_customer/dblect/__init__.py +0 -0
  217. dblect-0.1.0/tests/fixtures/scenarios/cases/revenue_by_customer/dblect/contracts.py +15 -0
  218. dblect-0.1.0/tests/fixtures/scenarios/cases/revenue_by_customer/expected.yml +6 -0
  219. dblect-0.1.0/tests/fixtures/scenarios/cases/revenue_by_customer/manifest.json +1 -0
  220. dblect-0.1.0/tests/fixtures/scenarios/cases/revenue_by_customer/overlay/models/marts/revenue_by_customer.sql +9 -0
  221. dblect-0.1.0/tests/fixtures/scenarios/cases/revenue_by_customer/story.md +39 -0
  222. dblect-0.1.0/tests/fixtures/scenarios/cases/total_daily_revenue/dblect/__init__.py +0 -0
  223. dblect-0.1.0/tests/fixtures/scenarios/cases/total_daily_revenue/dblect/contracts.py +9 -0
  224. dblect-0.1.0/tests/fixtures/scenarios/cases/total_daily_revenue/expected.yml +6 -0
  225. dblect-0.1.0/tests/fixtures/scenarios/cases/total_daily_revenue/manifest.json +1 -0
  226. dblect-0.1.0/tests/fixtures/scenarios/cases/total_daily_revenue/overlay/models/marts/total_daily_revenue.sql +9 -0
  227. dblect-0.1.0/tests/fixtures/scenarios/cases/total_daily_revenue/story.md +32 -0
  228. dblect-0.1.0/tests/fixtures/snapshot_audit/dbt_project.yml +20 -0
  229. dblect-0.1.0/tests/fixtures/snapshot_audit/manifest.json +1 -0
  230. dblect-0.1.0/tests/fixtures/snapshot_audit/models/renamed_safe.sql +6 -0
  231. dblect-0.1.0/tests/fixtures/snapshot_audit/models/renamed_unsafe.sql +5 -0
  232. dblect-0.1.0/tests/fixtures/snapshot_audit/models/safe_current.sql +4 -0
  233. dblect-0.1.0/tests/fixtures/snapshot_audit/models/safe_outer_filter.sql +10 -0
  234. dblect-0.1.0/tests/fixtures/snapshot_audit/models/unsafe_join.sql +5 -0
  235. dblect-0.1.0/tests/fixtures/snapshot_audit/seeds/raw_orders.csv +100 -0
  236. dblect-0.1.0/tests/fixtures/snapshot_audit/snapshots/orders_snapshot.sql +11 -0
  237. dblect-0.1.0/tests/fixtures/snapshot_audit/snapshots/orders_snapshot_renamed.sql +17 -0
  238. dblect-0.1.0/tests/flatten/__init__.py +0 -0
  239. dblect-0.1.0/tests/flatten/test_detector.py +145 -0
  240. dblect-0.1.0/tests/lineage/__init__.py +0 -0
  241. dblect-0.1.0/tests/lineage/_duckdb_oracle.py +61 -0
  242. dblect-0.1.0/tests/lineage/_lattice_laws.py +59 -0
  243. dblect-0.1.0/tests/lineage/conftest.py +24 -0
  244. dblect-0.1.0/tests/lineage/test_array_nonemptiness_propagation.py +319 -0
  245. dblect-0.1.0/tests/lineage/test_cardinality_cross_model.py +225 -0
  246. dblect-0.1.0/tests/lineage/test_conditional_activation.py +424 -0
  247. dblect-0.1.0/tests/lineage/test_config_facts.py +221 -0
  248. dblect-0.1.0/tests/lineage/test_demo_aggregation_depth.py +97 -0
  249. dblect-0.1.0/tests/lineage/test_demo_nullability.py +212 -0
  250. dblect-0.1.0/tests/lineage/test_domain_type_coherence.py +349 -0
  251. dblect-0.1.0/tests/lineage/test_domain_type_join_keys.py +84 -0
  252. dblect-0.1.0/tests/lineage/test_domain_type_lattice.py +210 -0
  253. dblect-0.1.0/tests/lineage/test_domain_type_outer_join.py +137 -0
  254. dblect-0.1.0/tests/lineage/test_domain_type_propagation.py +206 -0
  255. dblect-0.1.0/tests/lineage/test_facts_grounding.py +257 -0
  256. dblect-0.1.0/tests/lineage/test_facts_lattice.py +113 -0
  257. dblect-0.1.0/tests/lineage/test_facts_property.py +96 -0
  258. dblect-0.1.0/tests/lineage/test_facts_registry.py +130 -0
  259. dblect-0.1.0/tests/lineage/test_fanout_grain.py +49 -0
  260. dblect-0.1.0/tests/lineage/test_functional_dependency_lattice.py +198 -0
  261. dblect-0.1.0/tests/lineage/test_functional_dependency_propagation.py +523 -0
  262. dblect-0.1.0/tests/lineage/test_nested_and_unnest.py +229 -0
  263. dblect-0.1.0/tests/lineage/test_nullability_activation.py +143 -0
  264. dblect-0.1.0/tests/lineage/test_nullability_facts.py +303 -0
  265. dblect-0.1.0/tests/lineage/test_nullability_outer_join.py +179 -0
  266. dblect-0.1.0/tests/lineage/test_pbt_domain_type_soundness.py +183 -0
  267. dblect-0.1.0/tests/lineage/test_pbt_functional_dependency_soundness.py +361 -0
  268. dblect-0.1.0/tests/lineage/test_pbt_lineage.py +686 -0
  269. dblect-0.1.0/tests/lineage/test_pbt_nullability_monotone.py +154 -0
  270. dblect-0.1.0/tests/lineage/test_pbt_nullability_soundness.py +160 -0
  271. dblect-0.1.0/tests/lineage/test_pbt_uniqueness_soundness.py +363 -0
  272. dblect-0.1.0/tests/lineage/test_predicate_flow.py +253 -0
  273. dblect-0.1.0/tests/lineage/test_predicate_implication.py +368 -0
  274. dblect-0.1.0/tests/lineage/test_propagator.py +348 -0
  275. dblect-0.1.0/tests/lineage/test_reference_resolution.py +63 -0
  276. dblect-0.1.0/tests/lineage/test_resolution_coverage.py +217 -0
  277. dblect-0.1.0/tests/lineage/test_schema_accumulation.py +118 -0
  278. dblect-0.1.0/tests/lineage/test_semiring_laws.py +126 -0
  279. dblect-0.1.0/tests/lineage/test_shared_tree_resolution.py +120 -0
  280. dblect-0.1.0/tests/lineage/test_sketch_opacity.py +83 -0
  281. dblect-0.1.0/tests/lineage/test_surrogate_key_facts.py +180 -0
  282. dblect-0.1.0/tests/lineage/test_uniqueness_facts.py +364 -0
  283. dblect-0.1.0/tests/lineage/test_uniqueness_lattice.py +99 -0
  284. dblect-0.1.0/tests/lineage/test_uniqueness_propagation.py +400 -0
  285. dblect-0.1.0/tests/lineage/test_where_provenance.py +251 -0
  286. dblect-0.1.0/tests/manifest/__init__.py +0 -0
  287. dblect-0.1.0/tests/manifest/test_catalog.py +281 -0
  288. dblect-0.1.0/tests/manifest/test_compilation_status.py +94 -0
  289. dblect-0.1.0/tests/manifest/test_dag.py +156 -0
  290. dblect-0.1.0/tests/manifest/test_model_config.py +75 -0
  291. dblect-0.1.0/tests/manifest/test_parse.py +184 -0
  292. dblect-0.1.0/tests/nullability/__init__.py +0 -0
  293. dblect-0.1.0/tests/nullability/test_detector.py +461 -0
  294. dblect-0.1.0/tests/scenarios/__init__.py +0 -0
  295. dblect-0.1.0/tests/scenarios/test_scenarios.py +71 -0
  296. dblect-0.1.0/tests/snapshot/__init__.py +0 -0
  297. dblect-0.1.0/tests/snapshot/test_snapshot_temporal_filter.py +170 -0
  298. dblect-0.1.0/tests/sql/__init__.py +0 -0
  299. dblect-0.1.0/tests/sql/test_aggregates.py +114 -0
  300. dblect-0.1.0/tests/sql/test_jaffle.py +73 -0
  301. dblect-0.1.0/tests/sql/test_parse.py +41 -0
  302. dblect-0.1.0/tests/sql/test_patterns.py +1079 -0
  303. dblect-0.1.0/tests/sql/test_pbt_structural_hazards.py +247 -0
  304. dblect-0.1.0/tests/sql/test_result_statement.py +96 -0
  305. dblect-0.1.0/tests/sql/test_suppression_hint.py +20 -0
  306. dblect-0.1.0/tests/sql/test_vocab.py +197 -0
  307. dblect-0.1.0/tests/test_adapters.py +160 -0
  308. dblect-0.1.0/tests/test_analysis.py +105 -0
  309. dblect-0.1.0/tests/test_baseline.py +88 -0
  310. dblect-0.1.0/tests/test_fail_threshold.py +118 -0
  311. dblect-0.1.0/tests/test_packaging.py +91 -0
  312. dblect-0.1.0/tests/test_report.py +363 -0
  313. dblect-0.1.0/tests/test_sarif.py +332 -0
  314. dblect-0.1.0/tests/test_smoke.py +17 -0
  315. dblect-0.1.0/tests/test_templating.py +65 -0
  316. dblect-0.1.0/tests/types/__init__.py +0 -0
  317. dblect-0.1.0/tests/types/conftest.py +21 -0
  318. dblect-0.1.0/tests/types/test_contract_aggregation_e2e.py +140 -0
  319. dblect-0.1.0/tests/types/test_contract_bridge.py +416 -0
  320. dblect-0.1.0/tests/types/test_contract_integration.py +137 -0
  321. dblect-0.1.0/tests/types/test_contract_methods.py +200 -0
  322. dblect-0.1.0/tests/types/test_contract_registration.py +144 -0
  323. dblect-0.1.0/tests/types/test_domain_type_declaration.py +286 -0
  324. dblect-0.1.0/tests/types/test_foreign_key_edges.py +197 -0
  325. dblect-0.1.0/tests/types/test_loader.py +117 -0
  326. dblect-0.1.0/tests/types/test_pbt_declaration_equivalence.py +110 -0
  327. dblect-0.1.0/tests/uniqueness/__init__.py +0 -0
  328. dblect-0.1.0/tests/uniqueness/test_cross_model_fanout.py +284 -0
  329. dblect-0.1.0/tests/uniqueness/test_detector.py +816 -0
  330. dblect-0.1.0/tests/varinf/__init__.py +0 -0
  331. dblect-0.1.0/tests/varinf/test_op_vocabulary.py +51 -0
  332. dblect-0.1.0/tests/varinf/test_walker.py +221 -0
  333. dblect-0.1.0/tests/varinf/test_walker_properties.py +161 -0
  334. dblect-0.1.0/uv.lock +2668 -0
@@ -0,0 +1,65 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ check:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ # Single version to conserve Actions minutes; widen the matrix back to the
19
+ # full supported range (3.11-3.13) when budget allows.
20
+ python-version: ["3.13"]
21
+
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v3
27
+ with:
28
+ enable-cache: true
29
+ cache-dependency-glob: "uv.lock"
30
+
31
+ - name: Set up Python ${{ matrix.python-version }}
32
+ run: uv python install ${{ matrix.python-version }}
33
+
34
+ - name: Sync dependencies
35
+ run: uv sync --frozen --python ${{ matrix.python-version }}
36
+
37
+ - name: Lint (ruff check)
38
+ run: uv run ruff check
39
+
40
+ - name: Format check (ruff format)
41
+ run: uv run ruff format --check
42
+
43
+ - name: Type-check (pyright strict)
44
+ run: uv run pyright
45
+
46
+ # -n auto parallelises across the runner's cores; loadscope keeps each module on
47
+ # one worker so the module-scoped dbt-compile fixtures still run once apiece.
48
+ # Coverage instrumentation adds ~45% to the test step (measured) and nothing gates
49
+ # on it, so it runs only on the main push that publishes the artifact, keeping the
50
+ # PR feedback path on the uninstrumented run of the same tests.
51
+ - name: Tests (pytest)
52
+ if: github.event_name != 'push'
53
+ run: uv run pytest -n auto --dist loadscope
54
+
55
+ - name: Tests (pytest with coverage)
56
+ if: github.event_name == 'push'
57
+ run: uv run pytest -n auto --dist loadscope --cov --cov-report=term-missing --cov-report=xml
58
+
59
+ - name: Upload coverage artifact
60
+ if: github.event_name == 'push' && matrix.python-version == '3.13'
61
+ uses: actions/upload-artifact@v4
62
+ with:
63
+ name: coverage-xml
64
+ path: coverage.xml
65
+ if-no-files-found: error
@@ -0,0 +1,47 @@
1
+ name: release
2
+
3
+ # Publishes to PyPI when a v* tag is pushed. The wheel and sdist are built on a
4
+ # clean runner from the tagged commit, and upload uses PyPI Trusted Publishing
5
+ # (OIDC): no API token is stored anywhere. The `pypi` environment matches the
6
+ # trusted-publisher registration on PyPI and is the place to add a manual
7
+ # approval gate if one is ever wanted.
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ publish:
17
+ runs-on: ubuntu-latest
18
+ environment:
19
+ name: pypi
20
+ url: https://pypi.org/p/dblect
21
+ permissions:
22
+ id-token: write # required for OIDC; this is what authorizes the upload
23
+
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - name: Install uv
28
+ uses: astral-sh/setup-uv@v3
29
+
30
+ - name: Build wheel and sdist
31
+ run: uv build
32
+
33
+ - name: Guard tag against the packaged version
34
+ # The tag drives the release, but the wheel's version comes from
35
+ # _version.py via hatchling. If they disagree the upload would ship a
36
+ # surprise, so require the built wheel to carry the tag's version and
37
+ # fail loudly before publishing a mismatch.
38
+ run: |
39
+ tag="${GITHUB_REF_NAME#v}"
40
+ if [ ! -f "dist/dblect-${tag}-py3-none-any.whl" ]; then
41
+ echo "tag ${GITHUB_REF_NAME} has no matching wheel; built:" >&2
42
+ ls dist >&2
43
+ exit 1
44
+ fi
45
+
46
+ - name: Publish to PyPI
47
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,225 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ # /MANIFEST is the setuptools sdist artifact at repo root.
28
+ # Anchored to root so our src/dblect/manifest/ package is not matched
29
+ # case-insensitively on macOS/Windows filesystems.
30
+ /MANIFEST
31
+
32
+ # PyInstaller
33
+ # Usually these files are written by a python script from a template
34
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
35
+ *.manifest
36
+ *.spec
37
+
38
+ # Installer logs
39
+ pip-log.txt
40
+ pip-delete-this-directory.txt
41
+
42
+ # Unit test / coverage reports
43
+ htmlcov/
44
+ .tox/
45
+ .nox/
46
+ .coverage
47
+ .coverage.*
48
+ .cache
49
+ nosetests.xml
50
+ coverage.xml
51
+ *.cover
52
+ *.py.cover
53
+ .hypothesis/
54
+ .pytest_cache/
55
+ cover/
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+
77
+ # PyBuilder
78
+ .pybuilder/
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+
84
+ # IPython
85
+ profile_default/
86
+ ipython_config.py
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ # Pipfile.lock
99
+
100
+ # UV
101
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
102
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
103
+ # commonly ignored for libraries.
104
+ # uv.lock
105
+
106
+ # poetry
107
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
108
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
109
+ # commonly ignored for libraries.
110
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
111
+ # poetry.lock
112
+ # poetry.toml
113
+
114
+ # pdm
115
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
116
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
117
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
118
+ # pdm.lock
119
+ # pdm.toml
120
+ .pdm-python
121
+ .pdm-build/
122
+
123
+ # pixi
124
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
125
+ # pixi.lock
126
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
127
+ # in the .venv directory. It is recommended not to include this directory in version control.
128
+ .pixi
129
+
130
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
131
+ __pypackages__/
132
+
133
+ # Celery stuff
134
+ celerybeat-schedule
135
+ celerybeat.pid
136
+
137
+ # Redis
138
+ *.rdb
139
+ *.aof
140
+ *.pid
141
+
142
+ # RabbitMQ
143
+ mnesia/
144
+ rabbitmq/
145
+ rabbitmq-data/
146
+
147
+ # ActiveMQ
148
+ activemq-data/
149
+
150
+ # SageMath parsed files
151
+ *.sage.py
152
+
153
+ # Environments
154
+ .env
155
+ .envrc
156
+ .venv
157
+ env/
158
+ venv/
159
+ ENV/
160
+ env.bak/
161
+ venv.bak/
162
+
163
+ # Spyder project settings
164
+ .spyderproject
165
+ .spyproject
166
+
167
+ # Rope project settings
168
+ .ropeproject
169
+
170
+ # mkdocs documentation
171
+ /site
172
+
173
+ # mypy
174
+ .mypy_cache/
175
+ .dmypy.json
176
+ dmypy.json
177
+
178
+ # Pyre type checker
179
+ .pyre/
180
+
181
+ # pytype static type analyzer
182
+ .pytype/
183
+
184
+ # Cython debug symbols
185
+ cython_debug/
186
+
187
+ # PyCharm
188
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
189
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
190
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
191
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
192
+ # .idea/
193
+
194
+ # Abstra
195
+ # Abstra is an AI-powered process automation framework.
196
+ # Ignore directories containing user credentials, local state, and settings.
197
+ # Learn more at https://abstra.io/docs
198
+ .abstra/
199
+
200
+ # Visual Studio Code
201
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
202
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
203
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
204
+ # you could uncomment the following to ignore the entire vscode folder
205
+ # .vscode/
206
+ # Temporary file for partial code execution
207
+ tempCodeRunnerFile.py
208
+
209
+ # Ruff stuff:
210
+ .ruff_cache/
211
+
212
+ # PyPI configuration file
213
+ .pypirc
214
+
215
+ # Marimo
216
+ marimo/_static/
217
+ marimo/_lsp/
218
+ __marimo__/
219
+
220
+ # Streamlit
221
+ .streamlit/secrets.toml
222
+ HANDOFF.md
223
+
224
+ # Claude Code local worktrees
225
+ .claude/worktrees/
@@ -0,0 +1,134 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-07-11
11
+
12
+ The first published release: the base-world static analyzer, packaged for
13
+ `pip install dblect`. It reads a compiled dbt manifest, propagates column-level
14
+ facts through the DAG, and reports structural and declaration-level findings the
15
+ user can navigate to and act on. No execution and no LLM are required. The
16
+ runtime half (property-based execution, replay-determinism) and the
17
+ flag/var-world analysis are deferred to later releases.
18
+
19
+ ### Added
20
+
21
+ **Manifest and SQL ingestion**
22
+
23
+ - Typed dbt manifest ingestion: `Manifest`, `Node`, and a `Dag` with topology,
24
+ parsed from `manifest.json`.
25
+ - Analysis over each model's compiled SQL (not the raw Jinja), parsed once per
26
+ run and shared across detectors via a sqlglot wrapper.
27
+ - `catalog.json` ingestion for seed and source columns, so undocumented DAG
28
+ leaves resolve.
29
+ - Source-Jinja front end that discovers `var` and `env_var` references, and a
30
+ typed view of the manifest's macro registry.
31
+
32
+ **Column-level lineage substrate**
33
+
34
+ - A K-relations / semiring propagator carrying where-provenance through the DAG,
35
+ with CTE and UNION intermediates materialized in the lineage graph.
36
+ - The `lineage.facts` substrate: one property propagator over a shared fact
37
+ representation, with nullability discoverers backed by the manifest.
38
+ - Conditional facts that carry their guarding predicate, a predicate-implication
39
+ engine, and a predicate-flow property that accumulates each relation's row
40
+ filter, so conditional uniqueness, candidate keys, and `NOT NULL` activate
41
+ across relations and at intra-model scopes.
42
+ - Nested-field (STRUCT) lineage with explicit `UNNEST` grain.
43
+
44
+ **Uniqueness analysis**
45
+
46
+ - Uniqueness facts from dbt declarations and from structural proof, an
47
+ ordering-key detector and a join-fan-out detector over substrate keys, and
48
+ propagation of uniqueness through SQL operations and across model
49
+ dependencies.
50
+ - Candidate keys derived through surrogate-hash columns; seeds and snapshots
51
+ spanned as test targets.
52
+
53
+ **Domain-type contracts (the declaration DSL)**
54
+
55
+ - The authoring core: `DomainType`, `ModelContract`, `Field`, and the bridge
56
+ that lowers a contract to substrate facts.
57
+ - A contract and proxy layer: an expression AST, column proxies, the
58
+ `@contract` decorator, and fact constructors.
59
+ - Companion-binding column properties (the currency story), may/must refinement
60
+ for widened magnitudes, and domain-type transfer through joins
61
+ (functional-dependency-through-join, outer-join taint, join-key and fan-out
62
+ signals).
63
+ - A config discoverer that grounds an incremental model's `unique_key`, and
64
+ fact-level flag-world plumbing.
65
+ - Wider scalar field types in the classifier: `float` / `Float` as a magnitude,
66
+ `Timestamp` / `datetime` as inert siblings of `Date`, and a bare integer
67
+ (`int` / `Integer` / `BigInt`) accepted as opaque (inert) under the lenient
68
+ default, the ambiguous case a future strict mode rejects (#73).
69
+
70
+ **Structural hazard detectors**
71
+
72
+ - Structural detectors over every model: outer-join `WHERE` inversion,
73
+ non-determinism, the NULL-group-after-outer-join family (`GROUP BY`, join
74
+ key, `NOT IN`), and snapshot reads missing a temporal filter.
75
+ - Source-line provenance on every finding, and SQLFluff-compatible `-- noqa`
76
+ suppression. A bare `-- noqa` silences every dblect finding on its line; a
77
+ `-- noqa: DBLECT_<KIND>` directive silences one detector, and codes without the
78
+ `DBLECT_` prefix are left for `dbt lint` so one comment can address both tools.
79
+ This replaces the earlier bespoke `-- noqa-fixture:` syntax: dblect no longer owns
80
+ the suppression grammar, so it coexists with dbt Fusion's `dbt lint` rather than
81
+ competing with it. Every suppression is still logged in the report's `suppressed:`
82
+ section.
83
+
84
+ **Cross-world analysis**
85
+
86
+ - One analysis door (`dblect.analysis.analyze`) over both detector families,
87
+ returning every finding under one sealed type so a consumer cannot silently
88
+ drop a family.
89
+ - Incremental-worlds checking: compile a model's full-refresh and steady-state
90
+ forms data-free, run both detector families over each, and difference the
91
+ findings so a hazard living only in the unexercised branch surfaces.
92
+ - Coverage reporting that separates resolution (lineage the propagator could
93
+ follow) from grounding (columns a fact actually checks).
94
+
95
+ **CLI and reporting**
96
+
97
+ - The `dblect` CLI: `check` runs both detector families over a project, `init`
98
+ scaffolds the declaration tree and writes model stubs, `version` prints the
99
+ installed version.
100
+ - Text and JSON reporters under one versioned schema, with a non-zero exit on
101
+ unsuppressed findings and a `--no-fail` override. Status messages go to
102
+ stderr so stdout is a clean report.
103
+ - A validated-adapter gate, with `--dialect` as the operator's opt-in
104
+ acknowledgment that detector behavior is best-effort off the validated set.
105
+ - A library of demo scenarios: developer-introduced bugs that `dblect check`
106
+ catches.
107
+
108
+ **Execution harness**
109
+
110
+ - A DuckDB execution harness that runs dbt models via subprocess with fixture
111
+ overrides, the substrate the runtime layers will build on.
112
+
113
+ ### Changed
114
+
115
+ - `aggregation_not_well_typed` findings now name what the coherence guard
116
+ reasoned about: the aggregate and the column it reduced, the per-row companion
117
+ that is not held constant, and the grouping that fails to hold it, instead of
118
+ a generic message (#109).
119
+ - Aggregate behavior is now a first-class combine/select/count classification
120
+ (`dblect.sql.aggregates`), the single source of truth for both arming the
121
+ coherence guard and the not-well-typed finding. Keying on the sqlglot node type
122
+ covers every dialect at once; `min`/`max` are classified as selecting aggregates
123
+ and widen their result tag to top on a varying companion under the lenient
124
+ default (#115).
125
+
126
+ ### Fixed
127
+
128
+ - `count` (and `count_if`, `approx_count_distinct`) over a typed magnitude no
129
+ longer raises a spurious `aggregation_not_well_typed` finding. Conversely,
130
+ `stddev`, `variance`, `kurtosis`, `skewness`, `median`, `mode`, and the
131
+ quantile/percentile family now correctly flag a mixed-currency reduction (#115).
132
+
133
+ [Unreleased]: https://github.com/dvryaboy/dblect/compare/v0.1.0...HEAD
134
+ [0.1.0]: https://github.com/dvryaboy/dblect/releases/tag/v0.1.0
dblect-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,46 @@
1
+ # Code Style
2
+ We avoid stringy typing like the plague.
3
+ We use rigorous types.
4
+
5
+ We reach for an existing helper before writing a parallel one, and generalize it when a second caller needs a variant. A comment or commit message that reaches for "mirroring", "duplicate of", or "acceptable for now" is a signal to stop and reuse the original instead.
6
+
7
+ ## Testing
8
+ We test rigorously.
9
+ Whenever applicable, we design quality PBT tests, or otherwise leverage provers and exhaustive testing.
10
+ We do not write tests for tests' sakes. We avoid test theater.
11
+ We prefer to test at the boundaries instead of implementation specifics; tests should survive implementation
12
+ changes that do not change contracts.
13
+ Excessive use of mocking is a smell. Loads of boilerplate is a smell.
14
+ Pin contracts, not coincidences. If fragile cooperation is observed (two functions work correctly together only because of implementation details, not contract guarantees), fix the contracts in code rather than pinning the accidental behavior in a test.
15
+
16
+ We work test-first: the failing test (or the property / lattice-law spec) comes before the implementation, so it pins the intended contract rather than rationalizing whatever the code happens to do. When the contract is amenable, we design the property-based test and its generators first, before the code, so the generator is not shaped to fit the implementation.
17
+
18
+ A whole suite passing on its first run is a smell rather than a win: it usually means the tests are weak or were written after the code. When it happens, we prove the tests bite by injecting deliberate contract violations and confirming a test fails for each.
19
+
20
+ We enumerate the input space rather than sampling it: a condition over a closed type earns a test per value, and the common ones (`COUNT(*)` among the duplicate-sensitive aggregates) belong in that space rather than being deferred as edge cases.
21
+
22
+ ## Soundness
23
+ We discharge each predicate or guard to an exact decision procedure before coding it, with a counterexample that marks its edge and a test that pins it. A plausible structural proxy standing in for a precise semantic condition is where the subtle bugs live.
24
+
25
+ A soundness check that branches on a closed type decides every case explicitly, rather than handling one (a `CROSS` join) and letting the rest fall through to the wrong answer.
26
+
27
+ When new code consumes an existing fact or invariant, we re-establish that fact's soundness across its full input space, even when we did not write it. A new consumer can raise the stakes on a latent over-claim and turn it into a wrong answer.
28
+
29
+ ## Comments
30
+ We avoid comment bloat. We write comments that explain the why of things, or help understand particularly
31
+ complex bits of code and call attention to footguns.
32
+
33
+ Specific things to keep out of comments, docstrings, and prose:
34
+ - Phase or tier numbering ("Tier 0", "phase 1", etc.). The numbering is arbitrary and reorderable; refer to work streams by name (e.g., "static analyser", "replay-determinism check", "runtime PBT loop").
35
+ - Narrative counts of fixture or repo contents ("5 models + 3 seeds + 20 tests"). They go stale silently; if a number matters, assert it.
36
+ - References to ephemeral planning files such as `HANDOFF.md`. Those are session-to-session notes and should not be cited from code, tests, or docs.
37
+
38
+ ## Prose Style
39
+ All narrative documents and comments should avoid em-dashes and "not x. not y. z" phrasing.
40
+ We do not bash older or alternative approaches: we win by being clear and positive, and complimenting them / acknowledging good ideas and influences when appropriate. We are generous with compliments and praise. We put our work in the context of the broader field.
41
+
42
+ # Development
43
+ We run Python and its tooling through `uv` (`uv run python`, `uv run pytest`, `uv run ruff`, `uv run pyright`); a bare `python` is not on PATH. Quick probes go through `uv run python -c "..."` rather than a throwaway file.
44
+
45
+ Before pushing we run the full gate, which CI runs as separate steps: `uv run ruff check`, `uv run ruff format --check`, `uv run pyright`, `uv run pytest`.
46
+