terrine 0.1.0.dev1__tar.gz → 0.1.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (494) hide show
  1. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/CHANGELOG.md +159 -0
  2. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/PKG-INFO +43 -23
  3. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/README.md +42 -22
  4. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/run_overhead.py +12 -12
  5. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/workloads.py +8 -8
  6. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/backend-compatibility.md +1 -1
  7. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/candidate-anchors.md +5 -4
  8. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/common-feature-patterns.md +25 -40
  9. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/common-stack-interop.md +6 -1
  10. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/dataset-agent-tools.md +6 -2
  11. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/design-specs.md +8 -4
  12. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/design-validation.md +58 -0
  13. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/first-dataset.md +28 -6
  14. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/increment-compatibility.md +1 -1
  15. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/index.md +6 -3
  16. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/library-handoffs.md +128 -0
  17. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/limitations.md +2 -2
  18. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/materialization-runtime.md +1 -1
  19. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/ml-staging.md +3 -1
  20. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/public-api.md +56 -17
  21. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/semantic-layer-interop.md +1 -1
  22. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/sequential-panels.md +23 -19
  23. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/state-panels.md +15 -9
  24. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/temporal-attributes.md +8 -1
  25. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/temporal-engine.md +232 -91
  26. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/trajectories-survival.md +22 -22
  27. terrine-0.1.0.dev2/docs/reference/refusals.md +576 -0
  28. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/evals/agent_dataset_tools.yaml +2 -2
  29. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/bigquery/dataset.py +9 -9
  30. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/common_stack_interop.py +7 -7
  31. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/dataset_agent_tools.py +8 -11
  32. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/event_sequences.py +5 -5
  33. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/increment_parity.py +14 -14
  34. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/materialization_runtime.py +9 -9
  35. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/ml_staging.py +8 -8
  36. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/nonadditive_metrics.py +13 -13
  37. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/postgres/dataset.py +9 -9
  38. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/retention_and_winsorization.py +1 -1
  39. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/retention_metrics.py +5 -5
  40. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/sequential_trials.py +11 -13
  41. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/snowflake/dataset.py +9 -9
  42. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/state_panel.py +12 -14
  43. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/table_first_dataset.py +19 -18
  44. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/trajectories_survival.py +18 -19
  45. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/unverified_backend.py +5 -5
  46. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/.test_durations +352 -255
  47. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/agent/test_dataset_tool_runtime.py +7 -7
  48. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/backends/test_generic.py +19 -19
  49. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compat/test_native_catalog.py +7 -7
  50. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_boolean_outputs.py +8 -8
  51. terrine-0.1.0.dev2/integration/compile/test_design_describe.py +299 -0
  52. terrine-0.1.0.dev2/integration/compile/test_frame_runtime_window_export.py +350 -0
  53. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_retention.py +1 -1
  54. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_time_to_event.py +4 -4
  55. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/conformance/test_engines.py +16 -9
  56. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/execution/test_generic_provider.py +4 -4
  57. terrine-0.1.0.dev2/integration/handoffs/test_causal_grid.py +208 -0
  58. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/state_panels/test_state_panel_workflows.py +14 -14
  59. terrine-0.1.0.dev2/integration/test_model_from_tables.py +123 -0
  60. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_backend_capabilities.py +10 -10
  61. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_bigquery_admission.py +7 -7
  62. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_incremental_equivalence.py +31 -31
  63. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_ml_staging_duckdb.py +94 -23
  64. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_admission.py +7 -7
  65. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_execution.py +7 -7
  66. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_shapes.py +15 -15
  67. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_windows.py +21 -21
  68. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_runtime_api.py +15 -15
  69. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_saved_plan_restart.py +16 -16
  70. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snapshot_capture_bounds.py +7 -7
  71. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snapshot_partition_consistency.py +9 -9
  72. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snowflake_admission.py +7 -7
  73. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snowflake_parity.py +13 -13
  74. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/pyproject.toml +1 -1
  75. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tach.toml +7 -3
  76. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/__init__.py +18 -1
  77. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/adapters/increment.py +22 -18
  78. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/adapters/opentelemetry.py +9 -2
  79. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/definitions.py +12 -6
  80. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/design_schema.py +37 -10
  81. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/policy.py +35 -13
  82. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/schemas.py +5 -3
  83. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/tools.py +30 -17
  84. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/dialect.py +18 -0
  85. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/duckdb.py +88 -5
  86. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/numeric.py +14 -6
  87. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/timestamps.py +11 -3
  88. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/_asof.py +7 -9
  89. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/anchors.py +53 -34
  90. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/columns.py +117 -34
  91. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/cost.py +11 -8
  92. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/dispatch.py +2 -1
  93. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/layers.py +11 -6
  94. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/materialize.py +2 -1
  95. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/provenance.py +12 -9
  96. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/ranges.py +5 -3
  97. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/retention.py +1 -1
  98. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/_observations.py +7 -5
  99. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/calendar_comparison.py +20 -19
  100. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/cohorts.py +4 -5
  101. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/event_sequence.py +8 -7
  102. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/ltv.py +4 -4
  103. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/sequential.py +31 -33
  104. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/state_panel.py +12 -14
  105. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/time_to_event.py +9 -8
  106. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/data.py +334 -74
  107. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/export.py +10 -2
  108. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/manifest.py +43 -38
  109. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/reference.py +32 -7
  110. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/splits.py +115 -89
  111. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/errors.py +30 -4
  112. terrine-0.1.0.dev2/terrine/execution/gates.py +340 -0
  113. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/inputs.py +2 -1
  114. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/diff_diff.py +9 -6
  115. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/lifelines.py +17 -9
  116. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/preflight.py +26 -14
  117. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/surrogate.py +22 -7
  118. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/frame.py +171 -57
  119. terrine-0.1.0.dev2/terrine/metrics.py +154 -0
  120. terrine-0.1.0.dev2/terrine/model.py +746 -0
  121. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/orchestration/airflow.py +26 -7
  122. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/orchestration/dagster.py +29 -6
  123. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/recipes.py +105 -48
  124. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/api.py +46 -26
  125. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/backends/duckdb.py +3 -3
  126. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/control.py +7 -3
  127. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/graph.py +5 -5
  128. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/leases.py +9 -2
  129. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/planner.py +14 -6
  130. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/planning.py +19 -15
  131. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/saved_plan.py +18 -8
  132. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/snapshot.py +19 -12
  133. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/state.py +2 -2
  134. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/bind.py +53 -50
  135. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/io.py +31 -4
  136. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/models.py +19 -19
  137. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/rules.py +14 -10
  138. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/sql.py +2 -1
  139. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/baseline.yaml +11 -11
  140. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/scale.py +25 -8
  141. terrine-0.1.0.dev2/terrine/spec/anchors.py +249 -0
  142. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/dataset.py +54 -13
  143. terrine-0.1.0.dev2/terrine/spec/describe.py +735 -0
  144. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/design.py +80 -41
  145. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/expressions.py +161 -89
  146. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/io.py +86 -56
  147. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/panels.py +149 -68
  148. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/sequence.py +26 -21
  149. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/state_panel.py +100 -48
  150. terrine-0.1.0.dev2/terrine/spec/trajectory.py +174 -0
  151. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/transforms.py +5 -0
  152. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/validate.py +346 -129
  153. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/aggregate.py +164 -72
  154. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/attributes.py +36 -32
  155. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/daily.py +34 -43
  156. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/first.py +3 -3
  157. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/lookup.py +15 -10
  158. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/nonadditive.py +22 -10
  159. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/prefix.py +26 -20
  160. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/readiness.py +8 -2
  161. terrine-0.1.0.dev2/terrine/windows.py +84 -0
  162. terrine-0.1.0.dev2/tests/.test_durations +2033 -0
  163. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/_worlds.py +9 -9
  164. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/adapters/test_airflow.py +24 -0
  165. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/adapters/test_dagster.py +27 -1
  166. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/fixtures/catalog.yaml +7 -7
  167. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_inspect_design.py +57 -17
  168. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_materialize.py +1 -1
  169. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_plan_authorization.py +3 -3
  170. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_policy.py +56 -1
  171. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_schemas.py +8 -5
  172. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_numeric.py +53 -0
  173. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_timestamps.py +30 -0
  174. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/fixtures/catalog.yaml +23 -23
  175. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/test_increment_semantics.py +9 -1
  176. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_anchors.py +55 -24
  177. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_columns.py +87 -20
  178. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_completeness_columns.py +1 -1
  179. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_event_sequence.py +18 -18
  180. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_ltv.py +11 -11
  181. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_matched_t0.py +10 -10
  182. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_nonadditive.py +24 -24
  183. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_panels.py +76 -40
  184. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_persisted_layers.py +8 -8
  185. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_range_scaling.py +11 -10
  186. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_retention.py +14 -11
  187. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_state_panel.py +45 -25
  188. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_time_to_event.py +14 -14
  189. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/conftest.py +14 -0
  190. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_definition.py +21 -14
  191. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_manifest.py +71 -3
  192. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_runtime_staging.py +1 -1
  193. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_splits.py +30 -9
  194. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_staging.py +18 -2
  195. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_training_view.py +57 -14
  196. terrine-0.1.0.dev2/tests/execution/test_gates.py +392 -0
  197. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_inputs.py +9 -9
  198. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_preflight.py +14 -0
  199. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_surrogate.py +25 -0
  200. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_warehouse_preflight.py +19 -0
  201. terrine-0.1.0.dev2/tests/fixtures/definition_v2.json +1 -0
  202. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/interop.py +9 -9
  203. terrine-0.1.0.dev2/tests/fixtures/readme.py +73 -0
  204. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/winsorization.py +14 -14
  205. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/study_api_smoke.py +9 -9
  206. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/parity/test_increment_frames.py +14 -14
  207. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/test_event_sequences.py +5 -5
  208. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_api.py +8 -8
  209. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_backend_capabilities.py +1 -1
  210. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_control_ipc.py +3 -5
  211. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_graph.py +1 -1
  212. terrine-0.1.0.dev2/tests/runtime/test_input_refusals.py +194 -0
  213. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_layers.py +44 -11
  214. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_planning.py +1 -1
  215. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_saved_plan.py +9 -9
  216. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_snapshot.py +10 -10
  217. terrine-0.1.0.dev2/tests/semantic/test_catalog.py +280 -0
  218. terrine-0.1.0.dev2/tests/semantic/test_from_tables.py +686 -0
  219. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/semantic/test_rules_import.py +2 -2
  220. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/simulate/test_scale_generator.py +44 -8
  221. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_calendar_comparison.py +24 -19
  222. terrine-0.1.0.dev2/tests/spec/test_describe.py +358 -0
  223. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_design_io.py +148 -80
  224. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_expressions.py +0 -13
  225. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_nonadditive_spec.py +6 -2
  226. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_panels.py +18 -26
  227. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_recipes.py +213 -40
  228. terrine-0.1.0.dev2/tests/spec/test_refusals.py +477 -0
  229. terrine-0.1.0.dev2/tests/spec/test_retention.py +51 -0
  230. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_sequence.py +4 -1
  231. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_state_panel.py +22 -15
  232. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_validate.py +198 -76
  233. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_nonadditive.py +4 -4
  234. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_prefix_lookup.py +80 -5
  235. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_readiness.py +5 -0
  236. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_scd2_attributes.py +15 -7
  237. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_semantic_binding.py +45 -45
  238. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_sparse_state.py +10 -2
  239. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_window_aggregates.py +128 -8
  240. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_errors.py +0 -22
  241. terrine-0.1.0.dev2/tests/test_frame.py +211 -0
  242. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_frame_runtime.py +27 -2
  243. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_metadata.py +13 -9
  244. terrine-0.1.0.dev2/tests/test_public_docstrings.py +41 -0
  245. terrine-0.1.0.dev2/tests/test_windows.py +39 -0
  246. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/uv.lock +1 -1
  247. terrine-0.1.0.dev1/docs/reference/refusals.md +0 -500
  248. terrine-0.1.0.dev1/integration/compile/test_frame_runtime_window_export.py +0 -34
  249. terrine-0.1.0.dev1/integration/test_model_from_tables.py +0 -177
  250. terrine-0.1.0.dev1/terrine/execution/gates.py +0 -90
  251. terrine-0.1.0.dev1/terrine/model.py +0 -468
  252. terrine-0.1.0.dev1/terrine/spec/anchors.py +0 -122
  253. terrine-0.1.0.dev1/terrine/spec/trajectory.py +0 -144
  254. terrine-0.1.0.dev1/terrine/windows.py +0 -54
  255. terrine-0.1.0.dev1/tests/.test_durations +0 -1226
  256. terrine-0.1.0.dev1/tests/semantic/test_catalog.py +0 -458
  257. terrine-0.1.0.dev1/tests/spec/test_retention.py +0 -27
  258. terrine-0.1.0.dev1/tests/test_frame.py +0 -74
  259. terrine-0.1.0.dev1/tests/test_windows.py +0 -28
  260. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/CLA.md +0 -0
  261. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/ci.yml +0 -0
  262. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/cla.yml +0 -0
  263. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/release.yml +0 -0
  264. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/scale.yml +0 -0
  265. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/warehouse-backends.yml +0 -0
  266. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.gitignore +0 -0
  267. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.kata.toml +0 -0
  268. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.pre-commit-config.yaml +0 -0
  269. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.python-version +0 -0
  270. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.roborev.toml +0 -0
  271. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/AGENTS.md +0 -0
  272. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/CLAUDE.md +0 -0
  273. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/CONTRIBUTING.md +0 -0
  274. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/LICENSE +0 -0
  275. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/Makefile +0 -0
  276. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/NOTICE +0 -0
  277. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/THIRD_PARTY_NOTICES.txt +0 -0
  278. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/README.md +0 -0
  279. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/report.py +0 -0
  280. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/results/duckdb-scale-v2-small.json +0 -0
  281. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/run_duckdb.py +0 -0
  282. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/run_live.py +0 -0
  283. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/workloads/small.json +0 -0
  284. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/capabilities.md +0 -0
  285. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/cost-and-residency.md +0 -0
  286. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/orchestrators.md +0 -0
  287. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/shared-dataset-contract.md +0 -0
  288. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/synthetic-worlds.md +0 -0
  289. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/index.md +0 -0
  290. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/research/edge-case-designs.md +0 -0
  291. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/research/landscape.md +0 -0
  292. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/research/ohdsi-study-design.md +0 -0
  293. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/research/point-in-time-feature-apis.md +0 -0
  294. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/batch_scoring.py +0 -0
  295. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/first_dataset.py +0 -0
  296. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/orchestrators/airflow_materialization.py +0 -0
  297. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/orchestrators/dagster_materialization.py +0 -0
  298. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/orchestrators/dbt_core_after_build.py +0 -0
  299. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/agent/test_dataset_tool_example.py +0 -0
  300. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/agent/test_dataset_tool_flow.py +0 -0
  301. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/backends/test_engine_registry.py +0 -0
  302. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/backends/test_numeric.py +0 -0
  303. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_column_runtime_contracts.py +0 -0
  304. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_ltv.py +0 -0
  305. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/conftest.py +0 -0
  306. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/__init__.py +0 -0
  307. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/conftest.py +0 -0
  308. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/test_diff_diff.py +0 -0
  309. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/test_library_handoffs.py +0 -0
  310. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/test_lifelines.py +0 -0
  311. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/parity/test_increment_panel_parity.py +0 -0
  312. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/temporal/test_retention_bands.py +0 -0
  313. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/temporal/test_window_quotient_composition.py +0 -0
  314. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/test_independence.py +0 -0
  315. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/bigquery_fixtures.py +0 -0
  316. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/postgres_fixtures.py +0 -0
  317. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/snowflake_fixtures.py +0 -0
  318. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_bigquery_execution.py +0 -0
  319. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_bigquery_parity.py +0 -0
  320. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_duckdb_compiler_scale.py +0 -0
  321. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_duckdb_runtime_recovery.py +0 -0
  322. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_duckdb_scale.py +0 -0
  323. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_live_warehouse_scale.py +0 -0
  324. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_materialization_example.py +0 -0
  325. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_ml_staging_examples.py +0 -0
  326. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_orchestrator_adapters.py +0 -0
  327. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_planning.py +0 -0
  328. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_timestamps.py +0 -0
  329. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_request_replay_process.py +0 -0
  330. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_runtime_cli.py +0 -0
  331. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_runtime_control.py +0 -0
  332. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_runtime_cost_budget.py +0 -0
  333. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snowflake_execution.py +0 -0
  334. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_winsorization_definition.py +0 -0
  335. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/mkdocs.yml +0 -0
  336. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/scripts/mutate.py +0 -0
  337. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/_time.py +0 -0
  338. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/adapters/__init__.py +0 -0
  339. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/__init__.py +0 -0
  340. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/__init__.py +0 -0
  341. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/capabilities.py +0 -0
  342. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/__init__.py +0 -0
  343. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/bigquery.py +0 -0
  344. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/postgres.py +0 -0
  345. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/snowflake.py +0 -0
  346. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/extrema.py +0 -0
  347. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/joins.py +0 -0
  348. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/registry.py +0 -0
  349. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/sequences.py +0 -0
  350. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/__init__.py +0 -0
  351. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/_predicates.py +0 -0
  352. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/__init__.py +0 -0
  353. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/event_panel.py +0 -0
  354. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/life_table.py +0 -0
  355. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/matched_t0.py +0 -0
  356. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/__init__.py +0 -0
  357. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/training.py +0 -0
  358. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/transforms.py +0 -0
  359. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/__init__.py +0 -0
  360. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_bigquery.py +0 -0
  361. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_generic.py +0 -0
  362. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_metadata.py +0 -0
  363. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_native.py +0 -0
  364. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_postgres.py +0 -0
  365. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_publish.py +0 -0
  366. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_snowflake.py +0 -0
  367. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/backend.py +0 -0
  368. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/__init__.py +0 -0
  369. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/orchestration/__init__.py +0 -0
  370. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/py.typed +0 -0
  371. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/__init__.py +0 -0
  372. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/arrow.py +0 -0
  373. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/backends/base.py +0 -0
  374. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/changes.py +0 -0
  375. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/cli.py +0 -0
  376. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/cost.py +0 -0
  377. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/events.py +0 -0
  378. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/execute.py +0 -0
  379. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/gc.py +0 -0
  380. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/layers.py +0 -0
  381. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/materialize.py +0 -0
  382. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/owner.py +0 -0
  383. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/status.py +0 -0
  384. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/synchronization.py +0 -0
  385. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/__init__.py +0 -0
  386. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/time.py +0 -0
  387. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/__init__.py +0 -0
  388. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/generate.py +0 -0
  389. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/world.py +0 -0
  390. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/__init__.py +0 -0
  391. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/__init__.py +0 -0
  392. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/ranges.py +0 -0
  393. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/retention.py +0 -0
  394. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/sequence.py +0 -0
  395. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/sessionize.py +0 -0
  396. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/sketches.py +0 -0
  397. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/__init__.py +0 -0
  398. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/_timing.py +0 -0
  399. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/adapters/test_dbt_core_after_build.py +0 -0
  400. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/conftest.py +0 -0
  401. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_requests.py +0 -0
  402. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/__init__.py +0 -0
  403. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_engine_registry.py +0 -0
  404. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_generic.py +0 -0
  405. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_sequences.py +0 -0
  406. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/__init__.py +0 -0
  407. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/fixtures/increment.yaml +0 -0
  408. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/fixtures/same-name-collision.yaml +0 -0
  409. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/fixtures/unsupported-metric.yaml +0 -0
  410. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/test_semantic_interop.py +0 -0
  411. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/__init__.py +0 -0
  412. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_cohorts.py +0 -0
  413. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_cost_estimates.py +0 -0
  414. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_survival_bounds.py +0 -0
  415. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_winsorization.py +0 -0
  416. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/__init__.py +0 -0
  417. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_label_support.py +0 -0
  418. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_winsorization_definition.py +0 -0
  419. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/documentation.py +0 -0
  420. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/__init__.py +0 -0
  421. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_backend_provider.py +0 -0
  422. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_generic.py +0 -0
  423. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_snowflake_session.py +0 -0
  424. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_sql_verification.py +0 -0
  425. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/conftest.py +0 -0
  426. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_diff_diff.py +0 -0
  427. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_lifelines.py +0 -0
  428. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/__init__.py +0 -0
  429. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/baseline.py +0 -0
  430. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/materialized_frame_v1.json +0 -0
  431. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_definition.json +0 -0
  432. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_fitted_definition.json +0 -0
  433. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_fitted_transform.json +0 -0
  434. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_invocation.json +0 -0
  435. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_manifest.json +0 -0
  436. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_plan_request.json +0 -0
  437. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_saved_plan.json +0 -0
  438. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/test_baseline.py +0 -0
  439. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/__init__.py +0 -0
  440. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/distribution_smoke.py +0 -0
  441. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/postgres_scratch.py +0 -0
  442. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/test_benchmark_failure_reporting.py +0 -0
  443. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/test_live_scale_gate.py +0 -0
  444. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/parity/__init__.py +0 -0
  445. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/__init__.py +0 -0
  446. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/baseline.py +0 -0
  447. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/nonadditive.py +0 -0
  448. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/panels.py +0 -0
  449. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/ranges.py +0 -0
  450. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/retention.py +0 -0
  451. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/scale_metrics.py +0 -0
  452. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/test_baseline.py +0 -0
  453. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/test_panels.py +0 -0
  454. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/__init__.py +0 -0
  455. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_arrow_output.py +0 -0
  456. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_batch_cleanup.py +0 -0
  457. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_cancel_publication.py +0 -0
  458. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_cli.py +0 -0
  459. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_control_recovery.py +0 -0
  460. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_cost_estimates.py +0 -0
  461. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_duckdb_publication.py +0 -0
  462. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_execute.py +0 -0
  463. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_gc.py +0 -0
  464. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_imports.py +0 -0
  465. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_invalidation.py +0 -0
  466. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_leases.py +0 -0
  467. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_opentelemetry.py +0 -0
  468. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_planner.py +0 -0
  469. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_recovery_lease.py +0 -0
  470. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_request_replay.py +0 -0
  471. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_run_events.py +0 -0
  472. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_run_status.py +0 -0
  473. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_snapshot_lifecycle.py +0 -0
  474. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_state.py +0 -0
  475. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/semantic/__init__.py +0 -0
  476. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/semantic/test_metric_rules.py +0 -0
  477. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/semantic/test_sql.py +0 -0
  478. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/simulate/__init__.py +0 -0
  479. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/simulate/test_world.py +0 -0
  480. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/__init__.py +0 -0
  481. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_sketch_refusal.py +0 -0
  482. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/__init__.py +0 -0
  483. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_first_occurrence.py +0 -0
  484. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_range_scaling.py +0 -0
  485. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_retention.py +0 -0
  486. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_sequence.py +0 -0
  487. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_sessionize.py +0 -0
  488. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_sketches.py +0 -0
  489. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_common_stack_interop.py +0 -0
  490. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_distribution.py +0 -0
  491. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_documentation.py +0 -0
  492. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_engine_dispatch.py +0 -0
  493. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_mutation_runner.py +0 -0
  494. {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_optional_dependencies.py +0 -0
@@ -1,5 +1,164 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0.dev2
4
+
5
+ ### Performance
6
+
7
+ - Run a build's integrity checks, eligibility summary and output in one
8
+ statement. Window checks are boolean flag columns of the study's audit
9
+ relation instead of one statement per check that recomputed the metric
10
+ from the source tables; the deferred checks form one schedule in
11
+ registration order, folded into gate statements, and the final run's
12
+ counts and the eligibility summary ride on the output statement as a
13
+ one-row cross join that is decoded before the first batch leaves. Refusal
14
+ codes, their context (`failed_count` included) and the first refusal for a
15
+ given dataset are unchanged; an empty output still refuses a failing check
16
+ on an excluded candidate, and `Budget.max_rows` and `max_output_bytes`
17
+ count output rows and bytes only. On the repro (DuckDB 1.5.6; 10,000
18
+ units, 300,000 events, ten snapshots, three metrics; Apple M3 Pro, 12
19
+ cores) a warm build went from 14.0 s and eight statements over the source
20
+ tables to 0.8 s and one. Designs with as-of attributes or event-history
21
+ checks keep one statement per such check between gate statements.
22
+ Composition for runtime staging runs the schedule once and each staging
23
+ task reads its output with the summary alone;
24
+ `preview(..., bounded_count=N)` still runs every check unrestricted and
25
+ caps only its eligibility read.
26
+ - Compare DuckDB lookup timestamps natively: a microsecond cutoff and state
27
+ are their own as-of keys, a naive side is attached to UTC explicitly so
28
+ the session `TimeZone` cannot shift it, and a nanosecond axis keeps the
29
+ structured order key. Hold counts in native integers there: `BIGINT` 0/1
30
+ terms accumulated in `HUGEINT` and narrowed by `TRY_CAST`, so a count
31
+ beyond `int64` refuses `terrine.temporal.exact_overflow` rather than
32
+ wrapping; integer, decimal and float sums keep their `BIGNUM`
33
+ coefficients. A window's state carries only the fields its measure reads,
34
+ and an unfiltered measure's state no longer holds the five constant
35
+ unknown-filter counters. PostgreSQL, BigQuery, Snowflake and the portable
36
+ policy keep their numeric and timestamp state; their compiled statements
37
+ change only in the gate shape, the pruned state columns and integer count
38
+ literals.
39
+
40
+ ### Breaking changes
41
+
42
+ - Catalog documents are `schema_version: 2`. Sources use `time`, `units` and
43
+ `events`; the root event mapping is `events`; dimensions use `unit` and `key`,
44
+ measures use `event`, and metrics use `unit`. Version 1 catalogs refuse with
45
+ `terrine.catalog.invalid_definition` before other field diagnostics.
46
+ - Design and `DatasetDefinition` documents are version 2. Older saved designs,
47
+ definitions, and manifests or saved plans embedding them refuse; reconstruct
48
+ and save the design again. Manifest and saved-plan envelope versions stay 1.
49
+ - `Events(table, *, time, key, unit=None, metrics=None, labels=(), complete=None)`
50
+ replaces `keys=` and `values=` with one unit key and user-named metrics.
51
+ Generated metric names are removed; without `metrics=`, only the event is
52
+ declared. Labels, histories and events share a namespace, `__` is reserved
53
+ in labels, and `<source>_<column>` value-event names are reserved against
54
+ event, label and history declarations.
55
+ - `Model.from_tables` drops `readiness=` and takes completeness from each
56
+ `Events.complete` and `History.complete`; `con` is optional for in-memory
57
+ tables. `Model.from_catalog` retains `readiness=`.
58
+ - Event selectors and returned sequence metadata use `event`, not `fact`.
59
+ `Initiation` and `PersistentAdoption` take an event or label name without
60
+ `source`; its declaring source is resolved from the model.
61
+ - Calendar-date `Schedule.end` and `AnchorRange.end` are inclusive, so the same
62
+ literal bounds can produce additional anchors. Aware datetime ends remain
63
+ exact exclusive instants; relative windows and `TimeSplit` are unchanged.
64
+ - `Schedule.every` is `int | None`; calendar schedules set `months` instead.
65
+ Serialized schedules carry both fields with exactly one non-null value.
66
+ - Every recipe and `Design` can omit `unit` for a single-unit model.
67
+ `life_table(group_by={name: expression})` replaces `features=`.
68
+ `state_panel(periods=None)` derives whole periods from the schedule; an
69
+ explicit period count is a cap, not permission to truncate.
70
+ - Bound descriptors use `time_column`, `units`, `events`, `unit`,
71
+ `unit_columns`, `event` and `key`. Definition-hash payloads use the same
72
+ vocabulary, changing feature definition hashes and `Model.model_hash`.
73
+ - The refusal codes `terrine.model.generated_metric_collision` and
74
+ `terrine.model.multiple_event_units` are removed.
75
+ - Invalid constructor inputs now raise coded `TerrineError` (still a
76
+ `ValueError`) rather than bare `ValueError`, `TypeError`, `RuntimeError` or
77
+ `FileExistsError`. Context names the caller's parameter in `field` and uses
78
+ stable `reason` tokens; document decoders preserve a reconstructed record's
79
+ refusal. Pydantic records retain `ValidationError`. Agent tools expose the
80
+ same design and split refusal codes as direct callers.
81
+ - DuckDB output order may differ from anchor order for small studies; row
82
+ order is not a dataset contract. Sort explicitly when needed.
83
+ - New DuckDB runtime prefix layers store counts as Arrow `decimal128(38, 0)`
84
+ and cannot be read by older versions; older layers still restore and compose.
85
+
86
+ ### Public API
87
+
88
+ - Declare metrics with `tr.count`, `tr.sum`, `tr.mean` (per event or
89
+ `per="day"`), `tr.min`, `tr.max`, `tr.count_distinct` and `tr.ratio`.
90
+ Their `where=` mappings accept declared labels, scalar equality, list
91
+ membership, and `__ne`, `__in`, `__not_in`, `__gt`, `__gte`, `__lt`,
92
+ `__lte` and `__between`; multiple keys are a conjunction.
93
+ - `Events(labels=...)` binds typed event-time properties for filters,
94
+ `event_time`, `asof` and treatment. `History` binds as-of attributes through
95
+ range validity (`valid_from`, optional `valid_to`) or a change log
96
+ (`changed_at`) in `Model.from_tables(history=...)`.
97
+ - `UnitSource`, `Events` and `History` accept pandas/Polars DataFrames and
98
+ Arrow tables. Without a connection, `Model.from_tables` registers them in a
99
+ private in-process DuckDB under their mapping names; nothing is uploaded.
100
+ These models materialize only. Durable staging, reopening and refresh need
101
+ a file-backed DuckDB; refusals provide that route.
102
+ DataFrame detection accepts classes exposed from the public package module
103
+ as well as submodules, including pandas 3.1.
104
+ - `Model.to_catalog()` writes equivalent version-2 YAML for reuse with
105
+ `Model.from_catalog`.
106
+ - Unit inference applies wherever a Design meets a Model, including validation,
107
+ preview, build, snapshot, planning, staging and agent tools. Multiple units
108
+ require an explicit choice; inferred and explicit units share resolved dataset
109
+ identity.
110
+ - `Schedule`, `time_series` and `trial_panel` accept `"1mo"`, `"3mo"` and
111
+ `"1y"` calendar steps. Anchors advance from the original start and clamp to
112
+ month-end without cumulative drift. `count`, `instant` and `instants` expose
113
+ this arithmetic. Windows and other elapsed fields still require seconds,
114
+ minutes, hours, days or weeks; state and calendar-comparison panels require
115
+ a fixed elapsed cadence.
116
+ Normalized calendar steps must fit in 19 decimal digits, so accepted year
117
+ steps remain readable from their serialized month representation.
118
+ - `Frame.to_pandas(values_only=True)` and `to_polars(values_only=True)` retain
119
+ identity, role and structural value columns while dropping completeness,
120
+ reason and bookkeeping columns. Incomplete values remain null.
121
+ `training_set(...).to_pandas(values_only=True)` yields the in-memory feature
122
+ and label table. Observation metadata cannot be requested with this flag;
123
+ Arrow conversions remain lossless.
124
+ - `Design.describe(model)` validates and summarizes rows, anchors, expressions,
125
+ shape, treatment, completeness and output columns without querying source
126
+ rows. Agent design validation/proposal responses include the same summary.
127
+ - Unknown-name refusals include sorted `available` declarations, including
128
+ `model.metrics` for unknown metrics. Refusal messages identify the user's
129
+ input and a route forward; temporal errors identify the output and offending
130
+ bounds. New codes are `terrine.model.ambiguous_unit`,
131
+ `terrine.dataset.invalid_split`, `terrine.budget.invalid_limit`,
132
+ `terrine.runtime.invalid_configuration`, `terrine.export.invalid_input` and
133
+ `terrine.simulate.invalid_config`.
134
+ - Public constructors, recipes and conversion methods include concise
135
+ docstrings ending in an executable one-line example.
136
+ - Warehouse exports retain scoring rows when there are no outcome windows.
137
+
138
+ ### Documentation
139
+
140
+ - The README builds a customer spend forecasting table directly from DataFrames,
141
+ with user-named metrics, inclusive monthly snapshots, `describe` and executed
142
+ printed output; in-memory materialization and file-backed staging are distinct.
143
+ - The library hand-off guide builds a catalog-backed treatment/outcome grid of
144
+ trial panels for DoubleML, preserving repeated-unit clusters and distinct
145
+ nullable event labels; DoWhy's one-row-per-unit and weight restrictions remain
146
+ explicit. Terrine makes no causal claims.
147
+ - Guides cover `History`, metric constructors, catalog export, unit inference,
148
+ inclusive date bounds, calendar schedules, values-only views, training sets
149
+ and coded refusals.
150
+
151
+ ### Tests
152
+
153
+ - Catalog/design round trips, date-bound and calendar arithmetic oracles,
154
+ DataFrame/timezone parity, metric/filter/history contracts, inferred-unit
155
+ identity, values-only columns, metadata-only summaries, coded constructor
156
+ refusals and public docstring examples cover the API.
157
+ - Acceptance tests execute the README and file-backed staging tutorial and all
158
+ four causal-grid pairs, check distinct treated populations and outcome values,
159
+ and construct clustered `DoubleMLData` when the extra is installed.
160
+ - Refresh unit and integration duration records for balanced CI groups.
161
+
3
162
  ## 0.1.0.dev1
4
163
 
5
164
  ### Backend compatibility
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: terrine
3
- Version: 0.1.0.dev1
3
+ Version: 0.1.0.dev2
4
4
  Summary: Typed dataset construction and analytical workflows
5
5
  Project-URL: Homepage, https://github.com/kylejcaron/terrine
6
6
  Project-URL: Documentation, https://github.com/kylejcaron/terrine/tree/main/docs/guides
@@ -57,6 +57,9 @@ Description-Content-Type: text/markdown
57
57
 
58
58
  # terrine
59
59
 
60
+ > [!WARNING]
61
+ > Terrine is an experimental idea for simplifying dataset curation. It is pre-alpha: the API will change without notice, and it is not ready for production use.
62
+
60
63
  **Build datasets directly from event logs.**
61
64
 
62
65
  You have event tables: orders, sessions, payments. You want a modeling table, for example one row per customer per snapshot date, with features computed from the past and labels from the future. Terrine builds that table in your warehouse (DuckDB, PostgreSQL, BigQuery, Snowflake, and more) from a few lines of Python.
@@ -84,7 +87,6 @@ pip install --pre "terrine[duckdb,pandas]"
84
87
 
85
88
  <!-- --8<-- [start:first-dataset] -->
86
89
  ```python
87
- import ibis
88
90
  import pandas as pd
89
91
  import terrine as tr
90
92
 
@@ -97,32 +99,28 @@ orders = pd.DataFrame({
97
99
  "amount": [30.0, 45.0, 25.0, 60.0, 80.0],
98
100
  })
99
101
 
100
- con = ibis.duckdb.connect() # or your warehouse: ibis.bigquery.connect(...), ...
101
- con.create_table("customers", customers)
102
- con.create_table("orders", orders)
103
-
104
- # Tell Terrine who the rows are about and which dates the orders table covers.
102
+ # Who the rows are about, which table holds the events, what to measure,
103
+ # and which calendar days (inclusive) the orders table is fully loaded for.
105
104
  model = tr.Model.from_tables(
106
- con,
107
- units={"customer": tr.UnitSource("customers", "customer_id")},
105
+ units={"customer": tr.UnitSource(customers, "customer_id")},
108
106
  events={"orders": tr.Events(
109
- "orders", "ordered_at", {"customer": "customer_id"},
110
- values=("amount",), complete=("2025-01-01", "2025-03-20"),
107
+ orders, time="ordered_at", key="customer_id",
108
+ metrics={"orders": tr.count(), "spend": tr.sum("amount")},
109
+ complete=("2025-01-01", "2025-03-20"),
111
110
  )},
112
111
  )
113
112
 
114
- # One row per customer every 28 days: the last 30 days as features,
115
- # spend in the next 30 days as the target.
113
+ # One row per customer on the first of each month, February through March:
114
+ # the last 30 days as features, spend in the next 30 days as the target.
116
115
  design = tr.recipes.time_series(
117
- "customer", every="28d", start="2025-02-01", end="2025-03-02",
116
+ every="1mo", start="2025-02-01", end="2025-03-01",
118
117
  features={
119
- "orders_30d": tr.agg("orders_count", window=("-30d", "0s")),
120
- "spend_30d": tr.agg("orders_amount_sum", window=("-30d", "0s")),
121
- },
122
- outcomes={
123
- "spend_next_30d": tr.agg("orders_amount_sum", window=("0s", "30d")),
118
+ "orders_30d": tr.agg("orders", window=("-30d", "0s")),
119
+ "spend_30d": tr.agg("spend", window=("-30d", "0s")),
124
120
  },
121
+ outcomes={"spend_next_30d": tr.agg("spend", window=("0s", "30d"))},
125
122
  )
123
+ print(design.describe(model))
126
124
 
127
125
  data = design.build(model).to_pandas()
128
126
  columns = ["unit", "t0", "orders_30d", "spend_30d", "spend_next_30d", "spend_next_30d__reason"]
@@ -130,7 +128,29 @@ print(data[columns].assign(t0=data["t0"].dt.date).sort_values(["t0", "unit"]).to
130
128
  ```
131
129
  <!-- --8<-- [end:first-dataset] -->
132
130
 
131
+ `describe` explains the rows, windows and declared completeness without querying
132
+ the source tables ([sample summary](https://github.com/kylejcaron/terrine/blob/main/docs/guides/design-validation.md#describing-a-design)).
133
+ Running the example prints:
134
+
133
135
  ```text
136
+ One row per customer (table customer, key customer_id) at each snapshot.
137
+ Snapshots: every 1 month from 2025-02-01 through 2025-03-01 (2 dates: 2025-02-01, 2025-03-01), at
138
+ 00:00 UTC.
139
+ Features (before t0):
140
+ orders_30d: count of orders in [t0 - 30d, t0)
141
+ spend_30d: sum of orders.amount in [t0 - 30d, t0)
142
+ Outcomes (after t0):
143
+ spend_next_30d: sum of orders.amount in [t0, t0 + 30d)
144
+ Completeness: orders is declared complete for 2025-01-01 through 2025-03-20 (UTC days).
145
+ A metric window outside declared source coverage is null with reason incomplete_coverage; unit
146
+ observation bounds use incomplete_observation.
147
+ Empty complete metric windows (no matching events):
148
+ orders_30d: 0 (no reason)
149
+ spend_30d: 0 (no reason)
150
+ spend_next_30d: 0 (no reason)
151
+ Columns: unit, anchor_id, t0, orders_30d, orders_30d__complete, orders_30d__reason, spend_30d,
152
+ spend_30d__complete, spend_30d__reason, spend_next_30d, spend_next_30d__complete,
153
+ spend_next_30d__reason.
134
154
  unit t0 orders_30d spend_30d spend_next_30d spend_next_30d__reason
135
155
  alice 2025-02-01 2 75.0 25.0 <NA>
136
156
  bob 2025-02-01 1 80.0 0.0 <NA>
@@ -148,15 +168,15 @@ Reading the output:
148
168
  - **Alice's February 10 order** is in her February 1 target and her March 1 features. The same order is the future for one row and the past for the next.
149
169
  - **The March targets are unknown.** Their 30-day window runs past March 20, where the orders data ends. Drop them, keep them for scoring, or handle them however suits your model.
150
170
 
151
- Terrine names metrics after your table and columns: `orders_count`, `orders_amount_sum` and `orders_amount_mean`. Every value has a `<name>__complete` flag and a `<name>__reason` column, so you can filter or impute deliberately.
171
+ You name the metrics: `tr.count()` counts events, `tr.sum("amount")` adds a column, and `tr.count(where={"status": "refunded"})` counts only matching events once `status` is declared in `labels=`. Every value has a `<name>__complete` flag and a `<name>__reason` column, so you can filter or impute deliberately.
152
172
 
153
173
  ## The four ideas
154
174
 
155
175
  | Idea | In the example |
156
176
  | --- | --- |
157
177
  | **Unit:** who each row is about, read from a table so nobody silently drops out | `customers.customer_id` |
158
- | **t0:** the snapshot time of each row | every 28 days from 2025-02-01 |
159
- | **Windows:** relative to t0, start included, end excluded. Features must end at or before t0; outcomes must start at or after t0 | `("-30d", "0s")` is the 30 days before t0; `("0s", "30d")` is the 30 days after |
178
+ | **t0:** the snapshot time of each row. `every` is an elapsed duration (`"7d"`, `"4w"`) or a calendar step (`"1mo"`, `"3mo"`, `"1y"`); a monthly schedule from the 31st lands on Jan 31, Feb 28, Mar 31 | `every="1mo"`: the first of each month from 2025-02-01 through 2025-03-01, both days included |
179
+ | **Windows:** relative to t0, start included, end excluded. Features must end at or before t0; outcomes must start at or after t0. Windows are elapsed time: `"-1mo"` is refused, use `"-30d"` or `"-4w"` | `("-30d", "0s")` is the 30 days before t0; `("0s", "30d")` is the 30 days after |
160
180
  | **Completeness:** the calendar days, inclusive, that you declare fully loaded for each event table (not just the first and last event you happen to have). Windows outside them are unknown, not zero | `complete=("2025-01-01", "2025-03-20")` |
161
181
 
162
182
  ## Other datasets
@@ -177,7 +197,7 @@ More feature recipes are in [common feature patterns](https://github.com/kylejca
177
197
 
178
198
  ## Where it runs
179
199
 
180
- Queries run where your data lives; nothing is uploaded. Results come back as pandas, Polars or Arrow.
200
+ Queries run where your data lives; nothing is uploaded. DataFrames and Arrow tables, as above, are queried in a private in-process DuckDB. For a warehouse, pass its Ibis connection first and name the tables: `tr.Model.from_tables(con, units={"customer": tr.UnitSource("customers", "customer_id")}, events={"orders": tr.Events("orders", ...)})`. Results come back as pandas, Polars or Arrow.
181
201
 
182
202
  | Backend | Status |
183
203
  | --- | --- |
@@ -1,5 +1,8 @@
1
1
  # terrine
2
2
 
3
+ > [!WARNING]
4
+ > Terrine is an experimental idea for simplifying dataset curation. It is pre-alpha: the API will change without notice, and it is not ready for production use.
5
+
3
6
  **Build datasets directly from event logs.**
4
7
 
5
8
  You have event tables: orders, sessions, payments. You want a modeling table, for example one row per customer per snapshot date, with features computed from the past and labels from the future. Terrine builds that table in your warehouse (DuckDB, PostgreSQL, BigQuery, Snowflake, and more) from a few lines of Python.
@@ -27,7 +30,6 @@ pip install --pre "terrine[duckdb,pandas]"
27
30
 
28
31
  <!-- --8<-- [start:first-dataset] -->
29
32
  ```python
30
- import ibis
31
33
  import pandas as pd
32
34
  import terrine as tr
33
35
 
@@ -40,32 +42,28 @@ orders = pd.DataFrame({
40
42
  "amount": [30.0, 45.0, 25.0, 60.0, 80.0],
41
43
  })
42
44
 
43
- con = ibis.duckdb.connect() # or your warehouse: ibis.bigquery.connect(...), ...
44
- con.create_table("customers", customers)
45
- con.create_table("orders", orders)
46
-
47
- # Tell Terrine who the rows are about and which dates the orders table covers.
45
+ # Who the rows are about, which table holds the events, what to measure,
46
+ # and which calendar days (inclusive) the orders table is fully loaded for.
48
47
  model = tr.Model.from_tables(
49
- con,
50
- units={"customer": tr.UnitSource("customers", "customer_id")},
48
+ units={"customer": tr.UnitSource(customers, "customer_id")},
51
49
  events={"orders": tr.Events(
52
- "orders", "ordered_at", {"customer": "customer_id"},
53
- values=("amount",), complete=("2025-01-01", "2025-03-20"),
50
+ orders, time="ordered_at", key="customer_id",
51
+ metrics={"orders": tr.count(), "spend": tr.sum("amount")},
52
+ complete=("2025-01-01", "2025-03-20"),
54
53
  )},
55
54
  )
56
55
 
57
- # One row per customer every 28 days: the last 30 days as features,
58
- # spend in the next 30 days as the target.
56
+ # One row per customer on the first of each month, February through March:
57
+ # the last 30 days as features, spend in the next 30 days as the target.
59
58
  design = tr.recipes.time_series(
60
- "customer", every="28d", start="2025-02-01", end="2025-03-02",
59
+ every="1mo", start="2025-02-01", end="2025-03-01",
61
60
  features={
62
- "orders_30d": tr.agg("orders_count", window=("-30d", "0s")),
63
- "spend_30d": tr.agg("orders_amount_sum", window=("-30d", "0s")),
64
- },
65
- outcomes={
66
- "spend_next_30d": tr.agg("orders_amount_sum", window=("0s", "30d")),
61
+ "orders_30d": tr.agg("orders", window=("-30d", "0s")),
62
+ "spend_30d": tr.agg("spend", window=("-30d", "0s")),
67
63
  },
64
+ outcomes={"spend_next_30d": tr.agg("spend", window=("0s", "30d"))},
68
65
  )
66
+ print(design.describe(model))
69
67
 
70
68
  data = design.build(model).to_pandas()
71
69
  columns = ["unit", "t0", "orders_30d", "spend_30d", "spend_next_30d", "spend_next_30d__reason"]
@@ -73,7 +71,29 @@ print(data[columns].assign(t0=data["t0"].dt.date).sort_values(["t0", "unit"]).to
73
71
  ```
74
72
  <!-- --8<-- [end:first-dataset] -->
75
73
 
74
+ `describe` explains the rows, windows and declared completeness without querying
75
+ the source tables ([sample summary](https://github.com/kylejcaron/terrine/blob/main/docs/guides/design-validation.md#describing-a-design)).
76
+ Running the example prints:
77
+
76
78
  ```text
79
+ One row per customer (table customer, key customer_id) at each snapshot.
80
+ Snapshots: every 1 month from 2025-02-01 through 2025-03-01 (2 dates: 2025-02-01, 2025-03-01), at
81
+ 00:00 UTC.
82
+ Features (before t0):
83
+ orders_30d: count of orders in [t0 - 30d, t0)
84
+ spend_30d: sum of orders.amount in [t0 - 30d, t0)
85
+ Outcomes (after t0):
86
+ spend_next_30d: sum of orders.amount in [t0, t0 + 30d)
87
+ Completeness: orders is declared complete for 2025-01-01 through 2025-03-20 (UTC days).
88
+ A metric window outside declared source coverage is null with reason incomplete_coverage; unit
89
+ observation bounds use incomplete_observation.
90
+ Empty complete metric windows (no matching events):
91
+ orders_30d: 0 (no reason)
92
+ spend_30d: 0 (no reason)
93
+ spend_next_30d: 0 (no reason)
94
+ Columns: unit, anchor_id, t0, orders_30d, orders_30d__complete, orders_30d__reason, spend_30d,
95
+ spend_30d__complete, spend_30d__reason, spend_next_30d, spend_next_30d__complete,
96
+ spend_next_30d__reason.
77
97
  unit t0 orders_30d spend_30d spend_next_30d spend_next_30d__reason
78
98
  alice 2025-02-01 2 75.0 25.0 <NA>
79
99
  bob 2025-02-01 1 80.0 0.0 <NA>
@@ -91,15 +111,15 @@ Reading the output:
91
111
  - **Alice's February 10 order** is in her February 1 target and her March 1 features. The same order is the future for one row and the past for the next.
92
112
  - **The March targets are unknown.** Their 30-day window runs past March 20, where the orders data ends. Drop them, keep them for scoring, or handle them however suits your model.
93
113
 
94
- Terrine names metrics after your table and columns: `orders_count`, `orders_amount_sum` and `orders_amount_mean`. Every value has a `<name>__complete` flag and a `<name>__reason` column, so you can filter or impute deliberately.
114
+ You name the metrics: `tr.count()` counts events, `tr.sum("amount")` adds a column, and `tr.count(where={"status": "refunded"})` counts only matching events once `status` is declared in `labels=`. Every value has a `<name>__complete` flag and a `<name>__reason` column, so you can filter or impute deliberately.
95
115
 
96
116
  ## The four ideas
97
117
 
98
118
  | Idea | In the example |
99
119
  | --- | --- |
100
120
  | **Unit:** who each row is about, read from a table so nobody silently drops out | `customers.customer_id` |
101
- | **t0:** the snapshot time of each row | every 28 days from 2025-02-01 |
102
- | **Windows:** relative to t0, start included, end excluded. Features must end at or before t0; outcomes must start at or after t0 | `("-30d", "0s")` is the 30 days before t0; `("0s", "30d")` is the 30 days after |
121
+ | **t0:** the snapshot time of each row. `every` is an elapsed duration (`"7d"`, `"4w"`) or a calendar step (`"1mo"`, `"3mo"`, `"1y"`); a monthly schedule from the 31st lands on Jan 31, Feb 28, Mar 31 | `every="1mo"`: the first of each month from 2025-02-01 through 2025-03-01, both days included |
122
+ | **Windows:** relative to t0, start included, end excluded. Features must end at or before t0; outcomes must start at or after t0. Windows are elapsed time: `"-1mo"` is refused, use `"-30d"` or `"-4w"` | `("-30d", "0s")` is the 30 days before t0; `("0s", "30d")` is the 30 days after |
103
123
  | **Completeness:** the calendar days, inclusive, that you declare fully loaded for each event table (not just the first and last event you happen to have). Windows outside them are unknown, not zero | `complete=("2025-01-01", "2025-03-20")` |
104
124
 
105
125
  ## Other datasets
@@ -120,7 +140,7 @@ More feature recipes are in [common feature patterns](https://github.com/kylejca
120
140
 
121
141
  ## Where it runs
122
142
 
123
- Queries run where your data lives; nothing is uploaded. Results come back as pandas, Polars or Arrow.
143
+ Queries run where your data lives; nothing is uploaded. DataFrames and Arrow tables, as above, are queried in a private in-process DuckDB. For a warehouse, pass its Ibis connection first and name the tables: `tr.Model.from_tables(con, units={"customer": tr.UnitSource("customers", "customer_id")}, events={"orders": tr.Events("orders", ...)})`. Results come back as pandas, Polars or Arrow.
124
144
 
125
145
  | Backend | Status |
126
146
  | --- | --- |
@@ -79,20 +79,20 @@ WORKLOADS: tuple[Workload, ...] = (
79
79
  def _catalog() -> Catalog:
80
80
  return Catalog.model_validate(
81
81
  {
82
- "schema_version": 1,
82
+ "schema_version": 2,
83
83
  "day_boundary": "UTC",
84
84
  "dialect": "duckdb",
85
85
  "sources": {
86
86
  "purchases": {
87
87
  "relation": {"kind": "table", "name": "events"},
88
- "timestamp": "occurred_at",
89
- "entities": {"user": "user_id"},
90
- "facts": ["purchase", "amount", "price"],
88
+ "time": "occurred_at",
89
+ "units": {"user": "user_id"},
90
+ "events": ["purchase", "amount", "price"],
91
91
  "properties": [],
92
92
  "dimensions": [],
93
93
  }
94
94
  },
95
- "facts": {
95
+ "events": {
96
96
  "purchase": {"source": "purchases", "column": None},
97
97
  "amount": {"source": "purchases", "column": "amount"},
98
98
  "price": {"source": "purchases", "column": "price"},
@@ -100,17 +100,17 @@ def _catalog() -> Catalog:
100
100
  "properties": {},
101
101
  "dimensions": {},
102
102
  "measures": {
103
- "purchase_count": {"fact": "purchase", "aggregation": "count"},
104
- "amount_total": {"fact": "amount", "aggregation": "sum"},
105
- "price_avg": {"fact": "price", "aggregation": "avg_event"},
103
+ "purchase_count": {"event": "purchase", "aggregation": "count"},
104
+ "amount_total": {"event": "amount", "aggregation": "sum"},
105
+ "price_avg": {"event": "price", "aggregation": "avg_event"},
106
106
  },
107
107
  "metrics": {
108
- "purchases": {"kind": "mean", "entity": "user", "measure": "purchase_count"},
109
- "spend": {"kind": "mean", "entity": "user", "measure": "amount_total"},
110
- "avg_price": {"kind": "mean", "entity": "user", "measure": "price_avg"},
108
+ "purchases": {"kind": "mean", "unit": "user", "measure": "purchase_count"},
109
+ "spend": {"kind": "mean", "unit": "user", "measure": "amount_total"},
110
+ "avg_price": {"kind": "mean", "unit": "user", "measure": "price_avg"},
111
111
  "spend_per_purchase": {
112
112
  "kind": "ratio",
113
- "entity": "user",
113
+ "unit": "user",
114
114
  "numerator": "amount_total",
115
115
  "denominator": "purchase_count",
116
116
  },
@@ -48,20 +48,20 @@ def _catalog(dialect: str) -> Catalog:
48
48
  }
49
49
  return Catalog.model_validate(
50
50
  {
51
- "schema_version": 1,
51
+ "schema_version": 2,
52
52
  "day_boundary": "UTC",
53
53
  "dialect": dialect,
54
54
  "sources": {
55
55
  "events": {
56
56
  "relation": {"kind": "table", "name": "events"},
57
- "timestamp": "event_time",
58
- "entities": {"user": "unit_id"},
59
- "facts": ["value", "event"],
57
+ "time": "event_time",
58
+ "units": {"user": "unit_id"},
59
+ "events": ["value", "event"],
60
60
  "properties": ["adopted", "event_name"],
61
61
  "dimensions": [],
62
62
  }
63
63
  },
64
- "facts": {
64
+ "events": {
65
65
  "value": {"source": "events", "column": "value"},
66
66
  "event": {"source": "events", "column": None},
67
67
  },
@@ -76,11 +76,11 @@ def _catalog(dialect: str) -> Catalog:
76
76
  },
77
77
  "dimensions": {},
78
78
  "measures": {
79
- name: {"fact": "value", "aggregation": aggregation}
79
+ name: {"event": "value", "aggregation": aggregation}
80
80
  for name, aggregation in aggregations.items()
81
81
  },
82
82
  "metrics": {
83
- name: {"kind": "mean", "entity": "user", "measure": measure}
83
+ name: {"kind": "mean", "unit": "user", "measure": measure}
84
84
  for name, measure in (*((name, name) for name in aggregations), ("value", "total"))
85
85
  },
86
86
  }
@@ -137,7 +137,7 @@ def designs(config: ScaleConfig) -> dict[str, Design]:
137
137
  Schedule(
138
138
  "1d",
139
139
  (_EPOCH + timedelta(days=config.window_days)).date(),
140
- (_EPOCH + timedelta(days=config.window_days + config.anchor_periods)).date(),
140
+ (_EPOCH + timedelta(days=config.window_days + config.anchor_periods - 1)).date(),
141
141
  ),
142
142
  features=features,
143
143
  )
@@ -205,7 +205,7 @@ precision and refuses rather than narrows.
205
205
  magnitude sum (`γₖ = k · u / (1 − k · u)`), so a partition is admitted iff
206
206
  `B < limit · (1 − e)`. Otherwise its values are withheld before the backend
207
207
  adds them and every consumer of that state refuses: sums, averages and
208
- ratios with `float64_precision` (`bound_overflow`) for float facts or
208
+ ratios with `float64_precision` (`bound_overflow`) for float event values or
209
209
  `terrine.temporal.exact_overflow` for coefficients, cohort totals and means
210
210
  and cumulative trajectories with their existing overflow reasons. This
211
211
  bound is deliberately conservative: opposite-sign history whose magnitudes
@@ -2,16 +2,17 @@
2
2
 
3
3
  Use `design.build(model)` to compile a lazy Frame; materialization and staging run the required integrity checks through the owned execution path. Its candidate rows retain `unit`, `anchor_id`, exact `t0`, observation bounds and anchor provenance. Building does not execute source-data queries. Anchor integrity refusals expose stable `check_key` values such as `null_roster_key`, `duplicate_roster_key`, `null_event_id`, `duplicate_event_id`, `null_event_unit`, `null_event_timestamp`, `tied_event_timestamps` and `ambiguous_event_identity`. Correct the named source contract instead of silently deduplicating candidates.
4
4
 
5
- `FixedDate` and `Schedule` use the explicitly declared `UnitSource` roster for the design's logical unit. Roster entry/exit bounds are membership bounds evaluated as `[entered_at, exited_at)` at each candidate `t0`. Observation bounds are copied independently; a missing bound remains null and roster exit is not used as censoring. Fixed-date calendar bounds resolve through the Catalog's declared day boundary. Schedules advance by elapsed seconds from start and exclude their end.
5
+ `FixedDate` and `Schedule` use the explicitly declared `UnitSource` roster for the design's logical unit. Roster entry/exit bounds are membership bounds evaluated as `[entered_at, exited_at)` at each candidate `t0`. Observation bounds are copied independently; a missing bound remains null and roster exit is not used as censoring. Fixed-date calendar bounds resolve through the Catalog's declared day boundary. Schedules advance by elapsed seconds from the start day through the end day, which is included: the anchors are every `t0 = day(start) + k * every` before `day(end + 1 day)`.
6
6
 
7
- `EventAnchor` reads the declared fact's bound source and physical entity mapping. Fact values are not used as event-presence predicates: a row is an event even when its fact column is null. The source's `Model.event_ids` column is retained from raw relation rows for stable tie ordering and identity. Aware timestamp components of fallback or configured event identities are rendered after explicit UTC normalization, independent of the session timezone. `first` and `nth` are selected over full source history before `Design.anchor_range` filtering; `each` retains every event. A range is half-open; calendar-date endpoints resolve through Catalog day-boundary semantics and aware datetime endpoints remain exact UTC instants.
7
+ `EventAnchor` reads the named event's bound source and physical unit mapping. Event values are not used as event-presence predicates: a row is an event even when its event column is null. The source's `Model.event_ids` column is retained from raw relation rows for stable tie ordering and identity. Aware timestamp components of fallback or configured event identities are rendered after explicit UTC normalization, independent of the session timezone. `first` and `nth` are selected over full source history before `Design.anchor_range` filtering; `each` retains every event. A range starts at its `start` bound; a calendar-date `end` keeps the whole of that local day, resolved through Catalog day-boundary semantics, while an aware datetime `end` remains an exact exclusive UTC instant.
8
8
 
9
9
  ## Landmark event anchors
10
10
 
11
11
  `EventAnchor("purchase", offset="1d")` sets `t0` to the selected purchase
12
12
  instant plus one elapsed day. Offsets default to zero and accept non-negative
13
13
  integral seconds or `s`/`m`/`h`/`d`/`w` durations. Negative offsets refuse at
14
- construction with `terrine.design.invalid`. Selection and `anchor_range` use
14
+ construction with `terrine.design.invalid` (`reason="negative"`,
15
+ `field="offset"`). Selection and `anchor_range` use
15
16
  the **original event time**, not the landmark; the shift happens afterward.
16
17
  Roster membership and all feature/outcome windows use the resulting `t0`.
17
18
 
@@ -43,7 +44,7 @@ Frame materialization or a full rebuild instead.
43
44
  ## Source integrity
44
45
 
45
46
 
46
- An event anchor must name a fact declared by its bound source, not merely
47
+ An event anchor must name an event declared by its bound source, not merely
47
48
  present elsewhere in the Catalog; missing references use the same refusal
48
49
  as `Design.validate`. Unrelated source columns cannot change generated
49
50
  identities, including columns whose names differ only by case from a