smart-data-engine-sdk 0.1.0.dev0__tar.gz → 0.1.0rc1__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 (197) hide show
  1. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/PKG-INFO +31 -9
  2. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/README.md +27 -6
  3. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/pyproject.toml +8 -3
  4. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/__init__.py +94 -2
  5. smart_data_engine_sdk-0.1.0rc1/src/sde/_cutover_project.py +179 -0
  6. smart_data_engine_sdk-0.1.0rc1/src/sde/_local_state.py +188 -0
  7. smart_data_engine_sdk-0.1.0rc1/src/sde/_operator_deadline.py +50 -0
  8. smart_data_engine_sdk-0.1.0rc1/src/sde/_usage.py +314 -0
  9. smart_data_engine_sdk-0.1.0rc1/src/sde/bulk.py +79 -0
  10. smart_data_engine_sdk-0.1.0rc1/src/sde/cutover.py +286 -0
  11. smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_clickhouse_connection.py +224 -0
  12. smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_index_build.py +294 -0
  13. smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_operator.py +394 -0
  14. smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_staging.py +222 -0
  15. smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_storage.py +22 -0
  16. smart_data_engine_sdk-0.1.0rc1/src/sde/engines/_write_fences.py +271 -0
  17. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/engines/clickhouse.py +479 -53
  18. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/engines/orderbook.py +4 -1
  19. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/engines/postgres.py +332 -37
  20. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/errors.py +15 -0
  21. smart_data_engine_sdk-0.1.0rc1/src/sde/frozen_verification.py +152 -0
  22. smart_data_engine_sdk-0.1.0rc1/src/sde/generation.py +131 -0
  23. smart_data_engine_sdk-0.1.0rc1/src/sde/index_build.py +313 -0
  24. smart_data_engine_sdk-0.1.0rc1/src/sde/index_operator.py +347 -0
  25. smart_data_engine_sdk-0.1.0rc1/src/sde/inspection.py +62 -0
  26. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/layout.py +9 -0
  27. smart_data_engine_sdk-0.1.0rc1/src/sde/local_cutover.py +801 -0
  28. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/logging.py +11 -0
  29. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/migration.py +46 -10
  30. smart_data_engine_sdk-0.1.0rc1/src/sde/physical.py +531 -0
  31. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/placement.py +221 -29
  32. smart_data_engine_sdk-0.1.0rc1/src/sde/provisioning.py +63 -0
  33. smart_data_engine_sdk-0.1.0rc1/src/sde/query.py +521 -0
  34. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/schema.py +120 -24
  35. smart_data_engine_sdk-0.1.0rc1/src/sde/session.py +993 -0
  36. smart_data_engine_sdk-0.1.0rc1/src/sde/staging.py +264 -0
  37. smart_data_engine_sdk-0.1.0rc1/src/sde/staging_operator.py +393 -0
  38. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/telemetry.py +371 -20
  39. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/testing/memory.py +14 -1
  40. smart_data_engine_sdk-0.1.0rc1/src/sde/verification.py +220 -0
  41. smart_data_engine_sdk-0.1.0rc1/src/sde/write_fence.py +283 -0
  42. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/__init__.py +1 -0
  43. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/__main__.py +183 -0
  44. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/diagnostics.py +92 -0
  45. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/model.py +75 -0
  46. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/project.py +312 -0
  47. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/py.typed +0 -0
  48. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/query_count.py +301 -0
  49. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/resources.py +969 -0
  50. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/runtime.py +419 -0
  51. smart_data_engine_sdk-0.1.0rc1/src/sde_demo/verification.py +242 -0
  52. smart_data_engine_sdk-0.1.0rc1/src/sde_operator/__init__.py +1 -0
  53. smart_data_engine_sdk-0.1.0rc1/src/sde_operator/__main__.py +210 -0
  54. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/_claims.py +20 -6
  55. smart_data_engine_sdk-0.1.0rc1/tests/_cutover_driver.py +43 -0
  56. smart_data_engine_sdk-0.1.0rc1/tests/_cutover_worker.py +71 -0
  57. smart_data_engine_sdk-0.1.0rc1/tests/_generation.py +47 -0
  58. smart_data_engine_sdk-0.1.0rc1/tests/_index_worker.py +64 -0
  59. smart_data_engine_sdk-0.1.0rc1/tests/_staging_driver.py +49 -0
  60. smart_data_engine_sdk-0.1.0rc1/tests/_staging_worker.py +85 -0
  61. smart_data_engine_sdk-0.1.0rc1/tests/_weather_fixture.py +67 -0
  62. smart_data_engine_sdk-0.1.0rc1/tests/_write_fence.py +56 -0
  63. smart_data_engine_sdk-0.1.0rc1/tests/bulk_vectors.py +66 -0
  64. smart_data_engine_sdk-0.1.0rc1/tests/datetime_query_peer.py +31 -0
  65. smart_data_engine_sdk-0.1.0rc1/tests/fence_peer.py +44 -0
  66. smart_data_engine_sdk-0.1.0rc1/tests/qualification/__init__.py +1 -0
  67. smart_data_engine_sdk-0.1.0rc1/tests/qualification/metrics.py +115 -0
  68. smart_data_engine_sdk-0.1.0rc1/tests/qualification/operation_metrics.py +77 -0
  69. smart_data_engine_sdk-0.1.0rc1/tests/qualification/operation_worker.py +170 -0
  70. smart_data_engine_sdk-0.1.0rc1/tests/qualification/operations.py +221 -0
  71. smart_data_engine_sdk-0.1.0rc1/tests/qualification/operator_worker.py +38 -0
  72. smart_data_engine_sdk-0.1.0rc1/tests/qualification/packets.py +110 -0
  73. smart_data_engine_sdk-0.1.0rc1/tests/qualification/project.py +181 -0
  74. smart_data_engine_sdk-0.1.0rc1/tests/qualification/run.py +322 -0
  75. smart_data_engine_sdk-0.1.0rc1/tests/qualification/transition.py +145 -0
  76. smart_data_engine_sdk-0.1.0rc1/tests/qualification/weather_worker.py +236 -0
  77. smart_data_engine_sdk-0.1.0rc1/tests/test_bulk.py +313 -0
  78. smart_data_engine_sdk-0.1.0rc1/tests/test_bulk_live.py +289 -0
  79. smart_data_engine_sdk-0.1.0rc1/tests/test_clickhouse_datetime_queries.py +178 -0
  80. smart_data_engine_sdk-0.1.0rc1/tests/test_clickhouse_tls.py +435 -0
  81. smart_data_engine_sdk-0.1.0rc1/tests/test_clickhouse_transport.py +79 -0
  82. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_conformance.py +250 -1
  83. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_copy_freshness.py +3 -1
  84. smart_data_engine_sdk-0.1.0rc1/tests/test_cutover_plan.py +110 -0
  85. smart_data_engine_sdk-0.1.0rc1/tests/test_cutover_project.py +234 -0
  86. smart_data_engine_sdk-0.1.0rc1/tests/test_demo_query_count.py +505 -0
  87. smart_data_engine_sdk-0.1.0rc1/tests/test_demo_resources.py +634 -0
  88. smart_data_engine_sdk-0.1.0rc1/tests/test_demo_starter.py +707 -0
  89. smart_data_engine_sdk-0.1.0rc1/tests/test_demo_starter_live.py +145 -0
  90. smart_data_engine_sdk-0.1.0rc1/tests/test_demo_verification.py +436 -0
  91. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_dual_write.py +3 -1
  92. smart_data_engine_sdk-0.1.0rc1/tests/test_enrollment_durability.py +81 -0
  93. smart_data_engine_sdk-0.1.0rc1/tests/test_frozen_verification_live.py +162 -0
  94. smart_data_engine_sdk-0.1.0rc1/tests/test_generation_map.py +91 -0
  95. smart_data_engine_sdk-0.1.0rc1/tests/test_generation_migration_live.py +137 -0
  96. smart_data_engine_sdk-0.1.0rc1/tests/test_generation_session_live.py +159 -0
  97. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_implementer_documents.py +1 -1
  98. smart_data_engine_sdk-0.1.0rc1/tests/test_index_change_live.py +401 -0
  99. smart_data_engine_sdk-0.1.0rc1/tests/test_index_operator_live.py +1084 -0
  100. smart_data_engine_sdk-0.1.0rc1/tests/test_index_plan.py +95 -0
  101. smart_data_engine_sdk-0.1.0rc1/tests/test_integration_guard.py +48 -0
  102. smart_data_engine_sdk-0.1.0rc1/tests/test_local_cutover_live.py +995 -0
  103. smart_data_engine_sdk-0.1.0rc1/tests/test_map_unicode.py +101 -0
  104. smart_data_engine_sdk-0.1.0rc1/tests/test_map_unicode_live.py +26 -0
  105. smart_data_engine_sdk-0.1.0rc1/tests/test_measure_storage.py +164 -0
  106. smart_data_engine_sdk-0.1.0rc1/tests/test_measure_storage_live.py +64 -0
  107. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_migration.py +6 -4
  108. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_migration_live.py +3 -1
  109. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_no_account.py +15 -2
  110. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_no_retry.py +9 -3
  111. smart_data_engine_sdk-0.1.0rc1/tests/test_operation_metrics.py +102 -0
  112. smart_data_engine_sdk-0.1.0rc1/tests/test_operation_oracle.py +46 -0
  113. smart_data_engine_sdk-0.1.0rc1/tests/test_operator_cli.py +141 -0
  114. smart_data_engine_sdk-0.1.0rc1/tests/test_operator_deadline.py +60 -0
  115. smart_data_engine_sdk-0.1.0rc1/tests/test_operator_native_live.py +154 -0
  116. smart_data_engine_sdk-0.1.0rc1/tests/test_owned_session.py +179 -0
  117. smart_data_engine_sdk-0.1.0rc1/tests/test_owned_session_live.py +60 -0
  118. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_packaging.py +69 -2
  119. smart_data_engine_sdk-0.1.0rc1/tests/test_physical.py +413 -0
  120. smart_data_engine_sdk-0.1.0rc1/tests/test_physical_live.py +349 -0
  121. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_placement_and_routing.py +6 -1
  122. smart_data_engine_sdk-0.1.0rc1/tests/test_postgres_tls.py +117 -0
  123. smart_data_engine_sdk-0.1.0rc1/tests/test_qualification_cleanup.py +27 -0
  124. smart_data_engine_sdk-0.1.0rc1/tests/test_qualification_metrics.py +168 -0
  125. smart_data_engine_sdk-0.1.0rc1/tests/test_qualification_workers.py +30 -0
  126. smart_data_engine_sdk-0.1.0rc1/tests/test_query_live.py +447 -0
  127. smart_data_engine_sdk-0.1.0rc1/tests/test_query_plan.py +159 -0
  128. smart_data_engine_sdk-0.1.0rc1/tests/test_query_session.py +130 -0
  129. smart_data_engine_sdk-0.1.0rc1/tests/test_query_summary.py +75 -0
  130. smart_data_engine_sdk-0.1.0rc1/tests/test_query_vectors.py +87 -0
  131. smart_data_engine_sdk-0.1.0rc1/tests/test_release.py +473 -0
  132. smart_data_engine_sdk-0.1.0rc1/tests/test_runtime_privileges_live.py +366 -0
  133. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_schema_statements.py +1 -1
  134. smart_data_engine_sdk-0.1.0rc1/tests/test_session_ownership_live.py +364 -0
  135. smart_data_engine_sdk-0.1.0rc1/tests/test_staging_abandon_live.py +414 -0
  136. smart_data_engine_sdk-0.1.0rc1/tests/test_staging_native_live.py +125 -0
  137. smart_data_engine_sdk-0.1.0rc1/tests/test_staging_operator_live.py +696 -0
  138. smart_data_engine_sdk-0.1.0rc1/tests/test_staging_plan.py +84 -0
  139. smart_data_engine_sdk-0.1.0rc1/tests/test_storage_sizes_live.py +78 -0
  140. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_telemetry.py +136 -8
  141. smart_data_engine_sdk-0.1.0rc1/tests/test_telemetry_storage_time.py +372 -0
  142. smart_data_engine_sdk-0.1.0rc1/tests/test_verification_request.py +190 -0
  143. smart_data_engine_sdk-0.1.0rc1/tests/test_write_fence.py +172 -0
  144. smart_data_engine_sdk-0.1.0rc1/tests/test_write_fence_live.py +232 -0
  145. smart_data_engine_sdk-0.1.0rc1/tests/timestamp_peer.py +55 -0
  146. smart_data_engine_sdk-0.1.0rc1/tests/tls_certificates.py +143 -0
  147. smart_data_engine_sdk-0.1.0rc1/tests/verification_peer.py +31 -0
  148. smart_data_engine_sdk-0.1.0rc1/tests/weather_starter_peer.py +39 -0
  149. smart_data_engine_sdk-0.1.0.dev0/src/sde/session.py +0 -507
  150. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/.gitignore +0 -0
  151. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/LICENSE +0 -0
  152. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/NOTICE +0 -0
  153. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/canonical.py +0 -0
  154. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/capabilities.py +0 -0
  155. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/engines/__init__.py +0 -0
  156. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/entity.py +0 -0
  157. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/explain.py +0 -0
  158. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/groups.py +0 -0
  159. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/hashing.py +0 -0
  160. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/infer.py +0 -0
  161. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/internal.py +0 -0
  162. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/model.py +0 -0
  163. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/py.typed +0 -0
  164. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/routing.py +0 -0
  165. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/shapes.py +0 -0
  166. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/testing/__init__.py +0 -0
  167. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/testing/loader.py +0 -0
  168. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/types.py +0 -0
  169. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/src/sde/watermark.py +0 -0
  170. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_canonical.py +0 -0
  171. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_clickhouse_slice.py +0 -0
  172. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_compatibility_views.py +0 -0
  173. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_denormalized_layout.py +0 -0
  174. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_engine_agreement.py +0 -0
  175. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_explain_live.py +0 -0
  176. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_failure_semantics.py +0 -0
  177. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_groups_and_shapes.py +0 -0
  178. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_hashing.py +0 -0
  179. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_infer.py +0 -0
  180. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_internal_failures.py +0 -0
  181. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_layout_dialects.py +0 -0
  182. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_model.py +0 -0
  183. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_neutral_declaration.py +0 -0
  184. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_no_account_live.py +0 -0
  185. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_no_expiry.py +0 -0
  186. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_orderbook_adapter.py +0 -0
  187. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_orderbook_shape.py +0 -0
  188. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_orderbook_slice.py +0 -0
  189. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_overhead.py +0 -0
  190. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_platforms.py +0 -0
  191. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_postgres_slice.py +0 -0
  192. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_readme_claims.py +0 -0
  193. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_rollback_protection.py +0 -0
  194. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_rollback_protection_live.py +0 -0
  195. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_schema_vectors_live.py +0 -0
  196. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_session.py +0 -0
  197. {smart_data_engine_sdk-0.1.0.dev0 → smart_data_engine_sdk-0.1.0rc1}/tests/test_session_hashed.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: smart-data-engine-sdk
3
- Version: 0.1.0.dev0
3
+ Version: 0.1.0rc1
4
4
  Summary: Smart Data Engine client library: declare a data model, we place it and move it
5
5
  Project-URL: Homepage, https://github.com/Smart-Data-Engines/smart-data-engine-sdk
6
6
  Project-URL: Repository, https://github.com/Smart-Data-Engines/smart-data-engine-sdk
@@ -17,11 +17,12 @@ Classifier: Intended Audience :: Developers
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Topic :: Database
21
22
  Classifier: Typing :: Typed
22
- Requires-Python: <3.14,>=3.11
23
+ Requires-Python: <3.15,>=3.11
23
24
  Provides-Extra: clickhouse
24
- Requires-Dist: clickhouse-connect>=0.7; extra == 'clickhouse'
25
+ Requires-Dist: clickhouse-connect>=1.7.2; extra == 'clickhouse'
25
26
  Provides-Extra: dev
26
27
  Requires-Dist: mypy>=1.11; extra == 'dev'
27
28
  Requires-Dist: pytest>=8; extra == 'dev'
@@ -130,12 +131,9 @@ with zero releases — PyPI compares names after stripping `._-` and folding `l`
130
131
  `0`, and both reduce to `smartdataeng1ne`. The suffix changes the reduction, and it matches the
131
132
  repository name, which is the consistency worth having regardless.
132
133
 
133
- **`smart-data-engine-sdk` is unclaimed on PyPI, so those three commands install nothing today.** That
134
- is the honest state and not a typo: the library is not published yet. Read it as a warning rather
135
- than a footnote, because three error messages inside this library point at the same commands - if
136
- someone else registers the name before we do, a person who is already debugging gets told, by code
137
- they had decided to trust, to install a stranger's package.
138
- [`../docs/publishing.md`](../docs/publishing.md) is what closes this.
134
+ Published to PyPI on 12 September 2026. Until then this section was the one instruction in this
135
+ repository that was knowingly untrue, and it carried a warning saying so; the warning is gone because
136
+ the commands work.
139
137
 
140
138
  ## Conformance
141
139
 
@@ -150,3 +148,27 @@ its own. If it is not, that is a bug in the document.
150
148
  ## Licence
151
149
 
152
150
  Apache-2.0.
151
+
152
+ ## Bound migration verification
153
+
154
+ Managed cutover uses a `VerificationRequest` from the controller. Configure `Session(...,
155
+ project_id=...)` from your local enrollment manifest, load the request with
156
+ `VerificationRequest.from_record`, and pass it to `verify(..., request=request)`. The returned
157
+ `as_record()` includes the request and counts, while row-level differences remain local.
158
+ See [the request protocol](../docs/format-contract.md#7b-verification-requests-and-bound-reports).
159
+ Loaded placement maps are immutable; load a new document when their layout or routing changes.
160
+
161
+ Batch operations: [logical bulk writes](../docs/bulk-writes.md) document `save_many` / `saveMany`,
162
+ bounds, source/copy ordering and uncertain outcomes.
163
+
164
+ [Logical reads and exact summaries](../docs/logical-reads.md) cover scan, count and summarize,
165
+ including portable keyset pages, typed values and explicit failure semantics.
166
+
167
+ ## Local Weather starter
168
+
169
+ The [Weather starter](https://github.com/Smart-Data-Engines/smart-data-engine-sdk/blob/main/docs/weather-starter.md)
170
+ is part of this package, as the `sde-weather` command. It exercises logical operations with
171
+ restricted runtime credentials, and it includes local setup, telemetry, operator handoffs and an
172
+ ownership-checked reset.
173
+
174
+ Engine credentials and verified TLS configuration: [connection guide](../docs/engine-connections.md).
@@ -96,12 +96,9 @@ with zero releases — PyPI compares names after stripping `._-` and folding `l`
96
96
  `0`, and both reduce to `smartdataeng1ne`. The suffix changes the reduction, and it matches the
97
97
  repository name, which is the consistency worth having regardless.
98
98
 
99
- **`smart-data-engine-sdk` is unclaimed on PyPI, so those three commands install nothing today.** That
100
- is the honest state and not a typo: the library is not published yet. Read it as a warning rather
101
- than a footnote, because three error messages inside this library point at the same commands - if
102
- someone else registers the name before we do, a person who is already debugging gets told, by code
103
- they had decided to trust, to install a stranger's package.
104
- [`../docs/publishing.md`](../docs/publishing.md) is what closes this.
99
+ Published to PyPI on 12 September 2026. Until then this section was the one instruction in this
100
+ repository that was knowingly untrue, and it carried a warning saying so; the warning is gone because
101
+ the commands work.
105
102
 
106
103
  ## Conformance
107
104
 
@@ -116,3 +113,27 @@ its own. If it is not, that is a bug in the document.
116
113
  ## Licence
117
114
 
118
115
  Apache-2.0.
116
+
117
+ ## Bound migration verification
118
+
119
+ Managed cutover uses a `VerificationRequest` from the controller. Configure `Session(...,
120
+ project_id=...)` from your local enrollment manifest, load the request with
121
+ `VerificationRequest.from_record`, and pass it to `verify(..., request=request)`. The returned
122
+ `as_record()` includes the request and counts, while row-level differences remain local.
123
+ See [the request protocol](../docs/format-contract.md#7b-verification-requests-and-bound-reports).
124
+ Loaded placement maps are immutable; load a new document when their layout or routing changes.
125
+
126
+ Batch operations: [logical bulk writes](../docs/bulk-writes.md) document `save_many` / `saveMany`,
127
+ bounds, source/copy ordering and uncertain outcomes.
128
+
129
+ [Logical reads and exact summaries](../docs/logical-reads.md) cover scan, count and summarize,
130
+ including portable keyset pages, typed values and explicit failure semantics.
131
+
132
+ ## Local Weather starter
133
+
134
+ The [Weather starter](https://github.com/Smart-Data-Engines/smart-data-engine-sdk/blob/main/docs/weather-starter.md)
135
+ is part of this package, as the `sde-weather` command. It exercises logical operations with
136
+ restricted runtime credentials, and it includes local setup, telemetry, operator handoffs and an
137
+ ownership-checked reset.
138
+
139
+ Engine credentials and verified TLS configuration: [connection guide](../docs/engine-connections.md).
@@ -24,7 +24,7 @@ name = "smart-data-engine-sdk"
24
24
  dynamic = ["version"]
25
25
  description = "Smart Data Engine client library: declare a data model, we place it and move it"
26
26
  readme = "README.md"
27
- requires-python = ">=3.11,<3.14"
27
+ requires-python = ">=3.11,<3.15"
28
28
  license = "Apache-2.0"
29
29
  # Apache-2.0 section 4 asks that the licence travel with the work, and section 4(d) says the same
30
30
  # about a NOTICE file where one exists. Both were missing from the wheel and the sdist until this
@@ -47,6 +47,7 @@ classifiers = [
47
47
  "Programming Language :: Python :: 3.11",
48
48
  "Programming Language :: Python :: 3.12",
49
49
  "Programming Language :: Python :: 3.13",
50
+ "Programming Language :: Python :: 3.14",
50
51
  "Topic :: Database",
51
52
  # The wheel carries `py.typed`, so this row is a fact the artefact can be checked against.
52
53
  "Typing :: Typed",
@@ -68,13 +69,17 @@ Issues = "https://github.com/Smart-Data-Engines/smart-data-engine-sdk/issues"
68
69
  "Format contract" = "https://github.com/Smart-Data-Engines/smart-data-engine-sdk/blob/main/docs/format-contract.md"
69
70
  "Failure semantics" = "https://github.com/Smart-Data-Engines/smart-data-engine-sdk/blob/main/docs/failure-semantics.md"
70
71
 
72
+ [project.scripts]
73
+ sde-operator = "sde_operator.__main__:main"
74
+ sde-weather = "sde_demo.__main__:main"
75
+
71
76
  [project.optional-dependencies]
72
77
  signed = ["cryptography>=42"]
73
78
  postgres = ["psycopg[binary]>=3.1"]
74
79
  # The official ClickHouse client, over HTTP. Chosen over the native-protocol driver because HTTP is
75
80
  # what a client is most likely to already have open through a proxy or a load balancer, and because
76
81
  # the second engine should not also introduce a second network story.
77
- clickhouse = ["clickhouse-connect>=0.7"]
82
+ clickhouse = ["clickhouse-connect>=1.7.2"]
78
83
  dev = ["pytest>=8", "mypy>=1.11", "ruff>=0.6"]
79
84
 
80
85
  [tool.hatch.version]
@@ -84,7 +89,7 @@ path = "src/sde/__init__.py"
84
89
  # otherwise looks for a directory named after the project and fails - which it did, and better here
85
90
  # than at publish time.
86
91
  [tool.hatch.build.targets.wheel]
87
- packages = ["src/sde"]
92
+ packages = ["src/sde", "src/sde_operator", "src/sde_demo"]
88
93
  # `src/sde/py.typed` needs no entry here: hatchling includes it because it lives inside the package
89
94
  # directory, which was checked by building the wheel both ways rather than assumed. A `force-include`
90
95
  # for it was written first and then removed - dead configuration that looks load-bearing is worse than
@@ -33,22 +33,37 @@ mode, not a loophole.
33
33
 
34
34
  from __future__ import annotations
35
35
 
36
+ from .bulk import MAX_BATCH_ROWS, MAX_BATCH_VALUES, BulkWritable
36
37
  from .canonical import CanonicalError, canonical_bytes, canonical_str, digest16
37
38
  from .capabilities import members_of, satisfies
39
+ from .cutover import CUTOVER_PROTOCOL, CUTOVER_RELAYOUT_PROTOCOL, CutoverPlan, load_cutover_plan
38
40
  from .entity import Ref, clear_registry, entity, registry
39
41
  from .errors import (
42
+ BulkWriteRefused,
40
43
  DeclarationError,
41
44
  EngineError,
42
45
  MapError,
43
46
  MapRolledBack,
44
47
  MigrationRefused,
45
48
  ModelPlanningError,
49
+ ResourceBusy,
50
+ ResourceClosed,
46
51
  SdeError,
47
52
  )
48
53
  from .explain import Cost, Explains, PlanFinding, QueryPlan, QueryPlanRefused, explain
54
+ from .frozen_verification import FrozenVerifyReport, verify_frozen
49
55
  from .groups import Group, colocation_groups, group_of
50
56
  from .hashing import NameMap, hash_identifiers, load_or_create_salt
57
+ from .index_build import (
58
+ INDEX_CHANGE_PROTOCOL,
59
+ INDEX_PROTOCOL,
60
+ IndexPlan,
61
+ IndexReceipt,
62
+ index_build_name,
63
+ load_index_plan,
64
+ )
51
65
  from .infer import InferredModel, Note, infer_model, infer_models
66
+ from .inspection import InspectionContext
52
67
  from .internal import internal_failures, reset_internal_failures
53
68
  from .layout import (
54
69
  DIALECTS,
@@ -65,6 +80,7 @@ from .layout import (
65
80
  snake_case,
66
81
  stored_types,
67
82
  )
83
+ from .local_cutover import CutoverReceipt, CutoverRecoveryRequired, LocalCutover, load_local_map
68
84
  from .migration import (
69
85
  BACKFILL_TABLE,
70
86
  CHUNK_ROWS,
@@ -80,6 +96,7 @@ from .migration import (
80
96
  verify,
81
97
  )
82
98
  from .model import CONTRACT, LogicalModel, build_model, neutral_declaration
99
+ from .physical import PHYSICAL_DESIGN_SINCE, PhysicalFinding
83
100
  from .placement import (
84
101
  ALSO_WRITE_SINCE,
85
102
  MAP_CONTRACT,
@@ -91,10 +108,28 @@ from .placement import (
91
108
  PlacementMap,
92
109
  load_map,
93
110
  )
111
+ from .provisioning import prepare_schema
112
+ from .query import (
113
+ MAX_PAGE_ROWS,
114
+ NumericSummary,
115
+ Queryable,
116
+ QueryRefused,
117
+ Range,
118
+ ScanPage,
119
+ Summarizable,
120
+ )
94
121
  from .routing import Router, resolve
95
122
  from .schema import CompatibilityViews, compatibility_views, schema_is_fixed, schema_statements
96
- from .session import Engine, Session
123
+ from .session import Engine, ManagedEngine, Session
97
124
  from .shapes import SHAPE_KINDS, WRITE_KINDS, OperationShape, enumerate_shapes
125
+ from .staging import (
126
+ STAGING_PROTOCOL,
127
+ STAGING_RELAYOUT_PROTOCOL,
128
+ StagingPlan,
129
+ StagingReceipt,
130
+ load_staging_plan,
131
+ staging_table_name,
132
+ )
98
133
  from .telemetry import (
99
134
  MEASURED_FIELDS,
100
135
  CopyFreshness,
@@ -103,8 +138,12 @@ from .telemetry import (
103
138
  Histogram,
104
139
  Recorder,
105
140
  ShapeStats,
141
+ StorageMeasurement,
142
+ StorageSample,
143
+ StorageSize,
106
144
  Window,
107
145
  has_time_dimension,
146
+ time_fields,
108
147
  )
109
148
  from .types import Float32, Int32, Json, Timestamp, precision
110
149
  from .watermark import (
@@ -114,33 +153,51 @@ from .watermark import (
114
153
  WatermarkStore,
115
154
  enforce_forward_only,
116
155
  )
156
+ from .write_fence import EPOCH_COLUMN as WRITE_EPOCH_COLUMN
157
+ from .write_fence import FenceState, WriteFence
117
158
 
118
- __version__ = "0.1.0.dev0"
159
+ __version__ = "0.1.0rc1"
119
160
 
120
161
  __all__ = [
121
162
  "ALSO_WRITE_SINCE",
122
163
  "BACKFILL_TABLE",
123
164
  "CHUNK_ROWS",
124
165
  "CONTRACT",
166
+ "CUTOVER_PROTOCOL",
167
+ "CUTOVER_RELAYOUT_PROTOCOL",
125
168
  "DIALECTS",
126
169
  "DIALECT_PRECISION",
127
170
  "FIXED_SCHEMA",
171
+ "INDEX_CHANGE_PROTOCOL",
172
+ "INDEX_PROTOCOL",
128
173
  "MAP_CONTRACT",
129
174
  "MAP_CONTRACT_FLOOR",
175
+ "MAX_BATCH_ROWS",
176
+ "MAX_BATCH_VALUES",
177
+ "MAX_PAGE_ROWS",
130
178
  "MEASURED_FIELDS",
131
179
  "ORDERBOOK_KEY",
132
180
  "ORDERBOOK_SHAPE",
133
181
  "ORDERBOOK_TABLE",
182
+ "PHYSICAL_DESIGN_SINCE",
134
183
  "PRECISION_INDEPENDENT",
135
184
  "RESERVED_TABLES",
136
185
  "SHAPE_KINDS",
186
+ "STAGING_PROTOCOL",
187
+ "STAGING_RELAYOUT_PROTOCOL",
137
188
  "WATERMARK_TABLE",
189
+ "WRITE_EPOCH_COLUMN",
138
190
  "WRITE_KINDS",
139
191
  "BackfillProgress",
192
+ "BulkWritable",
193
+ "BulkWriteRefused",
140
194
  "CanonicalError",
141
195
  "CompatibilityViews",
142
196
  "CopyFreshness",
143
197
  "Cost",
198
+ "CutoverPlan",
199
+ "CutoverReceipt",
200
+ "CutoverRecoveryRequired",
144
201
  "DeclarationError",
145
202
  "DerivedLayout",
146
203
  "Difference",
@@ -149,15 +206,22 @@ __all__ = [
149
206
  "EntityProgress",
150
207
  "Explains",
151
208
  "FanOutStats",
209
+ "FenceState",
152
210
  "Float32",
211
+ "FrozenVerifyReport",
153
212
  "Group",
154
213
  "GroupFeatures",
155
214
  "GroupPlacement",
156
215
  "Histogram",
216
+ "IndexPlan",
217
+ "IndexReceipt",
157
218
  "InferredModel",
219
+ "InspectionContext",
158
220
  "Int32",
159
221
  "Json",
222
+ "LocalCutover",
160
223
  "LogicalModel",
224
+ "ManagedEngine",
161
225
  "MapError",
162
226
  "MapRolledBack",
163
227
  "Materialization",
@@ -166,24 +230,40 @@ __all__ = [
166
230
  "ModelPlanningError",
167
231
  "NameMap",
168
232
  "Note",
233
+ "NumericSummary",
169
234
  "OperationShape",
235
+ "PhysicalFinding",
170
236
  "PhysicalLayout",
171
237
  "PlacementMap",
172
238
  "PlanFinding",
173
239
  "Protection",
174
240
  "QueryPlan",
175
241
  "QueryPlanRefused",
242
+ "QueryRefused",
243
+ "Queryable",
244
+ "Range",
176
245
  "Recorder",
177
246
  "Ref",
247
+ "ResourceBusy",
248
+ "ResourceClosed",
178
249
  "Router",
250
+ "ScanPage",
179
251
  "SdeError",
180
252
  "Session",
181
253
  "ShapeStats",
254
+ "StagingPlan",
255
+ "StagingReceipt",
256
+ "StorageMeasurement",
257
+ "StorageSample",
258
+ "StorageSize",
259
+ "Summarizable",
182
260
  "Timestamp",
261
+ "VerificationRequest",
183
262
  "VerifyReport",
184
263
  "WatermarkCheck",
185
264
  "WatermarkStore",
186
265
  "Window",
266
+ "WriteFence",
187
267
  "__version__",
188
268
  "backfill",
189
269
  "build_model",
@@ -205,15 +285,21 @@ __all__ = [
205
285
  "group_of",
206
286
  "has_time_dimension",
207
287
  "hash_identifiers",
288
+ "index_build_name",
208
289
  "infer_model",
209
290
  "infer_models",
210
291
  "internal_failures",
292
+ "load_cutover_plan",
293
+ "load_index_plan",
294
+ "load_local_map",
211
295
  "load_map",
212
296
  "load_or_create_salt",
297
+ "load_staging_plan",
213
298
  "members_of",
214
299
  "neutral_declaration",
215
300
  "precision",
216
301
  "precision_refusal",
302
+ "prepare_schema",
217
303
  "registry",
218
304
  "reset_internal_failures",
219
305
  "resolve",
@@ -221,6 +307,12 @@ __all__ = [
221
307
  "schema_is_fixed",
222
308
  "schema_statements",
223
309
  "snake_case",
310
+ "staging_table_name",
224
311
  "stored_types",
312
+ "time_fields",
313
+ "verification_request",
225
314
  "verify",
315
+ "verify_frozen",
226
316
  ]
317
+
318
+ from .verification import VerificationRequest, verification_request
@@ -0,0 +1,179 @@
1
+ """Durable project state for a client-side cutover; no rows or credentials are stored here."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from . import _local_state
11
+ from .errors import MigrationRefused
12
+
13
+
14
+ def encode(value: Any) -> bytes:
15
+ # Native principal/namespace names are exact strings, not canonical map identifiers.
16
+ # Keep their spelling; the state checksum is over this explicitly versioned raw-JSON form.
17
+ return json.dumps(
18
+ value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
19
+ ).encode("utf-8")
20
+
21
+
22
+ def _abandoned_stages(payload: Any) -> bool:
23
+ stages = payload.get("stages") if isinstance(payload, dict) else None
24
+ if not isinstance(stages, dict):
25
+ return False
26
+ return any(
27
+ isinstance(record, dict)
28
+ and isinstance(record.get("receipt"), dict)
29
+ and record["receipt"].get("outcome") == "abandoned"
30
+ for record in stages.values()
31
+ )
32
+
33
+
34
+ def _index_changes(payload: Any) -> bool:
35
+ """Whether an in-place index record - executing or kept - also removes indexes (protocol 2)."""
36
+ if not isinstance(payload, dict):
37
+ return False
38
+ records: list[Any] = []
39
+ history = payload.get("indexes")
40
+ if isinstance(history, dict):
41
+ records.extend(history.values())
42
+ execution = payload.get("execution")
43
+ if isinstance(execution, dict) and execution.get("kind") == "index":
44
+ records.append(execution)
45
+ return any(
46
+ isinstance(record, dict)
47
+ and isinstance(record.get("plan"), dict)
48
+ and record["plan"].get("protocol") == 2
49
+ for record in records
50
+ )
51
+
52
+
53
+ class ProjectState:
54
+ """One POSIX directory and lock, shared by all local executors for this project."""
55
+
56
+ def __init__(self, root: Path, project_id: str, model_version: str) -> None:
57
+ self.root = root.resolve()
58
+ self.project_id = project_id
59
+ self.model_version = model_version
60
+ self.path = self.root / "project.json"
61
+ self.map_path = self.root / "active-map.json"
62
+
63
+ def lock(self) -> Any:
64
+ return _local_state.transaction(self.root)
65
+
66
+ def read(self) -> dict[str, Any]:
67
+ try:
68
+ envelope = json.loads(self.path.read_bytes())
69
+ if not isinstance(envelope, dict) or set(envelope) != {
70
+ "storage_contract",
71
+ "payload",
72
+ "sha256",
73
+ }:
74
+ raise ValueError("invalid state envelope")
75
+ if type(envelope["storage_contract"]) is not int or envelope[
76
+ "storage_contract"
77
+ ] not in (1, 2, 3, 4, 5):
78
+ raise ValueError("unsupported state storage contract")
79
+ payload = envelope["payload"]
80
+ if (
81
+ not isinstance(payload, dict)
82
+ or hashlib.sha256(encode(payload)).hexdigest() != envelope["sha256"]
83
+ ):
84
+ raise ValueError("state checksum mismatch")
85
+ if (
86
+ payload.get("project_id") != self.project_id
87
+ or payload.get("model_version") != self.model_version
88
+ ):
89
+ raise ValueError("state belongs to another local project/model")
90
+ fields = {
91
+ "project_id",
92
+ "model_version",
93
+ "active_map",
94
+ "execution",
95
+ "completed",
96
+ "retired_names",
97
+ }
98
+ if envelope["storage_contract"] >= 2:
99
+ fields.add("stages")
100
+ if not isinstance(payload.get("stages"), dict):
101
+ raise ValueError("staging history must be an object")
102
+ if envelope["storage_contract"] >= 3:
103
+ # In-place index builds: a reader of contracts 1 and 2 refuses this state rather
104
+ # than ignoring a history it would not know to keep.
105
+ fields.add("indexes")
106
+ if not isinstance(payload.get("indexes"), dict):
107
+ raise ValueError("index build history must be an object")
108
+ if envelope["storage_contract"] < 4 and _abandoned_stages(payload):
109
+ # Contract 4 is what makes an older operator refuse an abandoned staging's record
110
+ # instead of reading a receipt whose tables may name no identity.
111
+ raise ValueError("an abandoned staging needs state storage contract 4")
112
+ if envelope["storage_contract"] < 5 and _index_changes(payload):
113
+ # Contract 5 is what makes an older operator refuse a build that also removes
114
+ # indexes, instead of resuming it with no idea that removals follow the decision.
115
+ raise ValueError("an index change needs state storage contract 5")
116
+ if set(payload) != fields:
117
+ raise ValueError("unknown or missing project state fields")
118
+ return payload
119
+ except (OSError, ValueError, TypeError) as exc:
120
+ raise MigrationRefused(
121
+ "local cutover state is missing or corrupt; "
122
+ "restore verified state before proceeding"
123
+ ) from exc
124
+
125
+ def confirm(self) -> None:
126
+ # Call under the project lock. Neither reading matching bytes nor an in-memory receipt
127
+ # confirms the directory fsync that may have failed after an earlier publication.
128
+ _local_state.confirm_file(self.map_path)
129
+ _local_state.confirm_file(self.path)
130
+
131
+ def write(self, payload: dict[str, Any]) -> None:
132
+ body = encode(payload)
133
+ envelope = {
134
+ "storage_contract": 5
135
+ if _index_changes(payload)
136
+ else 4
137
+ if _abandoned_stages(payload)
138
+ else 3
139
+ if "indexes" in payload
140
+ else 2
141
+ if "stages" in payload
142
+ else 1,
143
+ "payload": payload,
144
+ "sha256": hashlib.sha256(body).hexdigest(),
145
+ }
146
+ _local_state.write_bytes(self.path, encode(envelope))
147
+
148
+ def enroll(self, document: dict[str, Any], map_payload: bytes) -> None:
149
+ with self.lock():
150
+ if self.map_path.exists() and self.map_path.read_bytes() != map_payload:
151
+ raise MigrationRefused("an existing active map differs from the enrolled state")
152
+ if self.path.exists():
153
+ state = self.read()
154
+ if (
155
+ state["active_map"] != document
156
+ or state["execution"] is not None
157
+ or state["completed"]
158
+ ):
159
+ raise MigrationRefused(
160
+ "local project is already enrolled; use its current state"
161
+ )
162
+ else:
163
+ state = {
164
+ "project_id": self.project_id,
165
+ "model_version": self.model_version,
166
+ "active_map": document,
167
+ "execution": None,
168
+ "completed": {},
169
+ "retired_names": [],
170
+ }
171
+ self.write(state)
172
+ if not self.map_path.exists():
173
+ _local_state.write_bytes(self.map_path, map_payload, replace=False)
174
+ # Matching visible files may come from a publication whose directory fsync failed.
175
+ # An identical retry must confirm their durability before enrollment succeeds.
176
+ self.confirm()
177
+
178
+ def publish(self, payload: bytes) -> None:
179
+ _local_state.write_bytes(self.map_path, payload)