mostlyright-data 0.9.0__py3-none-any.whl

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 (314) hide show
  1. mostlyright/data_harness/__init__.py +158 -0
  2. mostlyright/data_harness/acquisition/__init__.py +55 -0
  3. mostlyright/data_harness/acquisition/http.py +2773 -0
  4. mostlyright/data_harness/acquisition/parsing.py +809 -0
  5. mostlyright/data_harness/acquisition/ranges.py +495 -0
  6. mostlyright/data_harness/acquisition/result_download.py +360 -0
  7. mostlyright/data_harness/acquisition/retention_admission.py +248 -0
  8. mostlyright/data_harness/acquisition/sandbox.py +4888 -0
  9. mostlyright/data_harness/acquisition/url_policy.py +530 -0
  10. mostlyright/data_harness/agent_runtime.py +2743 -0
  11. mostlyright/data_harness/assets/logo-ink.svg +31 -0
  12. mostlyright/data_harness/backends/__init__.py +28 -0
  13. mostlyright/data_harness/backends/pandas_backend.py +350 -0
  14. mostlyright/data_harness/backends/polars_backend.py +366 -0
  15. mostlyright/data_harness/backends/protocol.py +124 -0
  16. mostlyright/data_harness/backends/reference.py +83 -0
  17. mostlyright/data_harness/backends/registry.py +55 -0
  18. mostlyright/data_harness/backends/restrictions.py +126 -0
  19. mostlyright/data_harness/canonical.py +333 -0
  20. mostlyright/data_harness/catalog_job.py +625 -0
  21. mostlyright/data_harness/cli.py +5398 -0
  22. mostlyright/data_harness/contracts.py +53 -0
  23. mostlyright/data_harness/coordinator.py +1307 -0
  24. mostlyright/data_harness/deploy.py +924 -0
  25. mostlyright/data_harness/deploy_target.py +312 -0
  26. mostlyright/data_harness/deployment_evidence.py +1067 -0
  27. mostlyright/data_harness/event_presentation.py +576 -0
  28. mostlyright/data_harness/events.py +2152 -0
  29. mostlyright/data_harness/fast_delimited.py +239 -0
  30. mostlyright/data_harness/fleet.py +237 -0
  31. mostlyright/data_harness/formats.py +236 -0
  32. mostlyright/data_harness/governors.py +1163 -0
  33. mostlyright/data_harness/hosted_bootstrap.py +972 -0
  34. mostlyright/data_harness/hosted_crawler.py +1115 -0
  35. mostlyright/data_harness/hosted_crawler_container_smoke.py +351 -0
  36. mostlyright/data_harness/hosted_crawler_fetch.py +423 -0
  37. mostlyright/data_harness/hosted_crawler_job.py +1277 -0
  38. mostlyright/data_harness/hosted_crawler_protocol.py +676 -0
  39. mostlyright/data_harness/hosted_dataset.py +1500 -0
  40. mostlyright/data_harness/hosted_deploy.py +3037 -0
  41. mostlyright/data_harness/hosted_handoff.py +62 -0
  42. mostlyright/data_harness/hosted_ingestion_contract.py +504 -0
  43. mostlyright/data_harness/hosted_ingestion_job.py +356 -0
  44. mostlyright/data_harness/hosted_ingestion_job_smoke.py +40 -0
  45. mostlyright/data_harness/hosted_session_container_smoke.py +194 -0
  46. mostlyright/data_harness/hosted_session_worker.py +3554 -0
  47. mostlyright/data_harness/hosted_session_worker_job_smoke.py +46 -0
  48. mostlyright/data_harness/hosted_worker.py +6784 -0
  49. mostlyright/data_harness/ingestion/__init__.py +56 -0
  50. mostlyright/data_harness/ingestion/contracts.py +461 -0
  51. mostlyright/data_harness/ingestion/faults.py +42 -0
  52. mostlyright/data_harness/ingestion/gcs_store.py +1162 -0
  53. mostlyright/data_harness/ingestion/spool.py +130 -0
  54. mostlyright/data_harness/ingestion/store.py +885 -0
  55. mostlyright/data_harness/key_seam.py +434 -0
  56. mostlyright/data_harness/linux_process_boundary.py +262 -0
  57. mostlyright/data_harness/local_contracts.py +2880 -0
  58. mostlyright/data_harness/local_search/__init__.py +5 -0
  59. mostlyright/data_harness/local_search/build_index.py +1087 -0
  60. mostlyright/data_harness/local_search/contracts.py +920 -0
  61. mostlyright/data_harness/local_search/query_trace.py +266 -0
  62. mostlyright/data_harness/local_search/retrieval.py +700 -0
  63. mostlyright/data_harness/local_search/sealed.py +474 -0
  64. mostlyright/data_harness/local_search/service.py +784 -0
  65. mostlyright/data_harness/nbrender/CONTRACT.md +212 -0
  66. mostlyright/data_harness/nbrender/__init__.py +12 -0
  67. mostlyright/data_harness/nbrender/chrome.py +359 -0
  68. mostlyright/data_harness/nbrender/code_body.py +266 -0
  69. mostlyright/data_harness/nbrender/document.py +407 -0
  70. mostlyright/data_harness/nbrender/frame.py +275 -0
  71. mostlyright/data_harness/nbrender/interactive.py +337 -0
  72. mostlyright/data_harness/nbrender/markdown_body.py +477 -0
  73. mostlyright/data_harness/nbrender/mr_components.py +134 -0
  74. mostlyright/data_harness/nbrender/outputs_data.py +595 -0
  75. mostlyright/data_harness/nbrender/outputs_rich.py +906 -0
  76. mostlyright/data_harness/nbrender/outputs_source.py +260 -0
  77. mostlyright/data_harness/nbrender/outputs_stage.py +176 -0
  78. mostlyright/data_harness/nbrender/outputs_text.py +400 -0
  79. mostlyright/data_harness/nbrender/parse.py +394 -0
  80. mostlyright/data_harness/nbrender/status.py +40 -0
  81. mostlyright/data_harness/nbrender/tokens.py +1295 -0
  82. mostlyright/data_harness/notebook.py +1710 -0
  83. mostlyright/data_harness/offline.py +2049 -0
  84. mostlyright/data_harness/operation_registry.py +1007 -0
  85. mostlyright/data_harness/operator_setup.py +239 -0
  86. mostlyright/data_harness/pipeline.py +6428 -0
  87. mostlyright/data_harness/plan_graph.py +2026 -0
  88. mostlyright/data_harness/preparation/__init__.py +104 -0
  89. mostlyright/data_harness/preparation/contracts.py +1017 -0
  90. mostlyright/data_harness/preparation/engine.py +221 -0
  91. mostlyright/data_harness/preparation/errors.py +14 -0
  92. mostlyright/data_harness/preparation/gates.py +751 -0
  93. mostlyright/data_harness/preparation/joins.py +574 -0
  94. mostlyright/data_harness/preparation/profile.py +384 -0
  95. mostlyright/data_harness/preparation/table.py +217 -0
  96. mostlyright/data_harness/preparation/transforms.py +568 -0
  97. mostlyright/data_harness/progress_events.py +534 -0
  98. mostlyright/data_harness/readers/__init__.py +46 -0
  99. mostlyright/data_harness/readers/containers.py +963 -0
  100. mostlyright/data_harness/readers/contracts.py +542 -0
  101. mostlyright/data_harness/readers/delimited.py +257 -0
  102. mostlyright/data_harness/readers/grib2/__init__.py +33 -0
  103. mostlyright/data_harness/readers/grib2/admission.py +722 -0
  104. mostlyright/data_harness/readers/grib2/decode.py +1009 -0
  105. mostlyright/data_harness/readers/grib2/geometry.py +1133 -0
  106. mostlyright/data_harness/readers/grib2/portable_math.py +501 -0
  107. mostlyright/data_harness/readers/json_tabular.py +485 -0
  108. mostlyright/data_harness/readers/registry.py +514 -0
  109. mostlyright/data_harness/readers/samples/README.md +110 -0
  110. mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/cities.csv.gz +0 -0
  111. mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/expected.json +24 -0
  112. mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/cities.csv.gz +0 -0
  113. mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/expected.json +24 -0
  114. mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/cities.tar +0 -0
  115. mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/expected.json +24 -0
  116. mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/cities.tar +0 -0
  117. mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/expected.json +24 -0
  118. mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/cities.zip +0 -0
  119. mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/expected.json +25 -0
  120. mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
  121. mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/expected.json +25 -0
  122. mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
  123. mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/expected.json +25 -0
  124. mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/cities.csv +3 -0
  125. mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/expected.json +23 -0
  126. mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/cities.tsv +5 -0
  127. mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/expected.json +25 -0
  128. mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/expected.json +30 -0
  129. mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/observations.csv +5 -0
  130. mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/expected.json +44 -0
  131. mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/stations.json +1 -0
  132. mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/expected.json +48 -0
  133. mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/observations.ndjson +4 -0
  134. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/cities.xlsx +0 -0
  135. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/expected.json +24 -0
  136. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/expected.json +27 -0
  137. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
  138. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/expected.json +27 -0
  139. mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
  140. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/README.md +20 -0
  141. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/expected.json +55 -0
  142. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/gfs-2m-temperature.grib2 +0 -0
  143. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/expected.json +54 -0
  144. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
  145. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/expected.json +54 -0
  146. mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/hrrr-categorical-rain.grib2 +0 -0
  147. mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/expected.json +54 -0
  148. mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
  149. mostlyright/data_harness/readers/samples.py +582 -0
  150. mostlyright/data_harness/readers/spreadsheet.py +803 -0
  151. mostlyright/data_harness/readers/tabular.py +510 -0
  152. mostlyright/data_harness/recipe.py +5321 -0
  153. mostlyright/data_harness/repair/__init__.py +78 -0
  154. mostlyright/data_harness/repair/adapters.py +274 -0
  155. mostlyright/data_harness/repair/contracts.py +872 -0
  156. mostlyright/data_harness/repair/coordinator.py +1099 -0
  157. mostlyright/data_harness/repair/errors.py +16 -0
  158. mostlyright/data_harness/review.py +2533 -0
  159. mostlyright/data_harness/rowset.py +283 -0
  160. mostlyright/data_harness/serving.py +1975 -0
  161. mostlyright/data_harness/serving_edge.py +590 -0
  162. mostlyright/data_harness/serving_http.py +1031 -0
  163. mostlyright/data_harness/session_probes.py +759 -0
  164. mostlyright/data_harness/signing.py +101 -0
  165. mostlyright/data_harness/source_discovery.py +898 -0
  166. mostlyright/data_harness/sources/__init__.py +209 -0
  167. mostlyright/data_harness/sources/_adapter_steps.py +213 -0
  168. mostlyright/data_harness/sources/adapters.py +1214 -0
  169. mostlyright/data_harness/sources/cadence.py +1428 -0
  170. mostlyright/data_harness/sources/cadence_emission.py +453 -0
  171. mostlyright/data_harness/sources/cadence_history.py +546 -0
  172. mostlyright/data_harness/sources/catalog/__init__.py +17 -0
  173. mostlyright/data_harness/sources/catalog/admission.py +477 -0
  174. mostlyright/data_harness/sources/catalog/authoring.py +1701 -0
  175. mostlyright/data_harness/sources/catalog/authoring_policy.py +701 -0
  176. mostlyright/data_harness/sources/catalog/authoring_shards.py +1217 -0
  177. mostlyright/data_harness/sources/catalog/bounded_io.py +231 -0
  178. mostlyright/data_harness/sources/catalog/channel.py +523 -0
  179. mostlyright/data_harness/sources/catalog/channel_client.py +296 -0
  180. mostlyright/data_harness/sources/catalog/contracts.py +825 -0
  181. mostlyright/data_harness/sources/catalog/coverage.py +137 -0
  182. mostlyright/data_harness/sources/catalog/delta.py +1340 -0
  183. mostlyright/data_harness/sources/catalog/embedding.py +532 -0
  184. mostlyright/data_harness/sources/catalog/entry_v2.py +1182 -0
  185. mostlyright/data_harness/sources/catalog/fill.py +3889 -0
  186. mostlyright/data_harness/sources/catalog/fill_partitions.py +459 -0
  187. mostlyright/data_harness/sources/catalog/fill_staging.py +1105 -0
  188. mostlyright/data_harness/sources/catalog/gating.py +374 -0
  189. mostlyright/data_harness/sources/catalog/generation_receipt.py +1607 -0
  190. mostlyright/data_harness/sources/catalog/harvest/__init__.py +7 -0
  191. mostlyright/data_harness/sources/catalog/harvest/ckan.py +384 -0
  192. mostlyright/data_harness/sources/catalog/harvest/datagov_v4.py +798 -0
  193. mostlyright/data_harness/sources/catalog/harvest/protocol.py +964 -0
  194. mostlyright/data_harness/sources/catalog/harvest/sdmx.py +445 -0
  195. mostlyright/data_harness/sources/catalog/harvest/stac.py +384 -0
  196. mostlyright/data_harness/sources/catalog/health.py +447 -0
  197. mostlyright/data_harness/sources/catalog/hosted_catalog.py +105 -0
  198. mostlyright/data_harness/sources/catalog/identity_history.py +1549 -0
  199. mostlyright/data_harness/sources/catalog/neural.py +1618 -0
  200. mostlyright/data_harness/sources/catalog/packed_catalog.py +2345 -0
  201. mostlyright/data_harness/sources/catalog/packed_retrieval.py +1517 -0
  202. mostlyright/data_harness/sources/catalog/packed_writer.py +2802 -0
  203. mostlyright/data_harness/sources/catalog/query_trace.py +1037 -0
  204. mostlyright/data_harness/sources/catalog/recommend.py +171 -0
  205. mostlyright/data_harness/sources/catalog/retrieval.py +230 -0
  206. mostlyright/data_harness/sources/catalog/retrieval_manifest.py +995 -0
  207. mostlyright/data_harness/sources/catalog/rights_decisions.py +254 -0
  208. mostlyright/data_harness/sources/catalog/sealed.py +560 -0
  209. mostlyright/data_harness/sources/catalog/search.py +230 -0
  210. mostlyright/data_harness/sources/catalog/streaming_delta.py +1097 -0
  211. mostlyright/data_harness/sources/catalog/update.py +891 -0
  212. mostlyright/data_harness/sources/collections.py +815 -0
  213. mostlyright/data_harness/sources/contracts.py +2223 -0
  214. mostlyright/data_harness/sources/deletion.py +761 -0
  215. mostlyright/data_harness/sources/fitness.py +162 -0
  216. mostlyright/data_harness/sources/governance.py +163 -0
  217. mostlyright/data_harness/sources/hosted.py +173 -0
  218. mostlyright/data_harness/sources/integration.py +218 -0
  219. mostlyright/data_harness/sources/range_reader.py +418 -0
  220. mostlyright/data_harness/sources/registry.py +514 -0
  221. mostlyright/data_harness/sources/rights_rule.py +59 -0
  222. mostlyright/data_harness/sources/source_cadence_vectors.v1.json +1 -0
  223. mostlyright/data_harness/sources/sports.py +521 -0
  224. mostlyright/data_harness/sources/stream.py +524 -0
  225. mostlyright/data_harness/sources/stream_connector.py +418 -0
  226. mostlyright/data_harness/sources/stream_recorder.py +1404 -0
  227. mostlyright/data_harness/studio_boundary.py +2019 -0
  228. mostlyright/data_harness/thin/__init__.py +37 -0
  229. mostlyright/data_harness/thin/acquire.py +1137 -0
  230. mostlyright/data_harness/thin/acquire_cancel.py +579 -0
  231. mostlyright/data_harness/thin/approvals.py +617 -0
  232. mostlyright/data_harness/thin/commands.py +406 -0
  233. mostlyright/data_harness/thin/download.py +194 -0
  234. mostlyright/data_harness/thin/narrative.py +589 -0
  235. mostlyright/data_harness/thin/parity.py +1070 -0
  236. mostlyright/data_harness/thin/propose.py +2759 -0
  237. mostlyright/data_harness/thin/research.py +1663 -0
  238. mostlyright/data_harness/thin/router.py +924 -0
  239. mostlyright/data_harness/thin/runs.py +519 -0
  240. mostlyright/data_harness/thin/session.py +281 -0
  241. mostlyright/data_harness/thin/stream.py +501 -0
  242. mostlyright/data_harness/thin/transport.py +187 -0
  243. mostlyright/data_harness/thin/vocabulary.py +368 -0
  244. mostlyright/data_harness/thin/workers.py +164 -0
  245. mostlyright/data_harness/ucum/TABLE-PIN.json +40 -0
  246. mostlyright/data_harness/ucum/ucum-subset.v1.json +632 -0
  247. mostlyright/data_harness/unit_flow.py +927 -0
  248. mostlyright/data_harness/units.py +572 -0
  249. mostlyright/data_harness/ux/__init__.py +9 -0
  250. mostlyright/data_harness/ux/approve.py +485 -0
  251. mostlyright/data_harness/ux/author_yaml.py +597 -0
  252. mostlyright/data_harness/ux/cloud_auth.py +447 -0
  253. mostlyright/data_harness/ux/commands/__init__.py +260 -0
  254. mostlyright/data_harness/ux/commands/approve.py +136 -0
  255. mostlyright/data_harness/ux/commands/auth.py +744 -0
  256. mostlyright/data_harness/ux/commands/author.py +79 -0
  257. mostlyright/data_harness/ux/commands/catalog_author.py +403 -0
  258. mostlyright/data_harness/ux/commands/catalog_fill.py +523 -0
  259. mostlyright/data_harness/ux/commands/catalog_harvest.py +545 -0
  260. mostlyright/data_harness/ux/commands/catalog_publish.py +1838 -0
  261. mostlyright/data_harness/ux/commands/catalog_search.py +71 -0
  262. mostlyright/data_harness/ux/commands/catalog_update.py +437 -0
  263. mostlyright/data_harness/ux/commands/deploy.py +134 -0
  264. mostlyright/data_harness/ux/commands/deploy_dataset.py +98 -0
  265. mostlyright/data_harness/ux/commands/deploy_plan.py +105 -0
  266. mostlyright/data_harness/ux/commands/deploy_status.py +104 -0
  267. mostlyright/data_harness/ux/commands/diff.py +74 -0
  268. mostlyright/data_harness/ux/commands/index.py +84 -0
  269. mostlyright/data_harness/ux/commands/inventory.py +47 -0
  270. mostlyright/data_harness/ux/commands/list_builds.py +143 -0
  271. mostlyright/data_harness/ux/commands/login.py +63 -0
  272. mostlyright/data_harness/ux/commands/peek.py +236 -0
  273. mostlyright/data_harness/ux/commands/plan_check.py +90 -0
  274. mostlyright/data_harness/ux/commands/preflight.py +97 -0
  275. mostlyright/data_harness/ux/commands/record.py +107 -0
  276. mostlyright/data_harness/ux/commands/review_setup.py +47 -0
  277. mostlyright/data_harness/ux/commands/search.py +440 -0
  278. mostlyright/data_harness/ux/commands/show.py +61 -0
  279. mostlyright/data_harness/ux/commands/whoami.py +37 -0
  280. mostlyright/data_harness/ux/credential_native.py +551 -0
  281. mostlyright/data_harness/ux/credential_store.py +1055 -0
  282. mostlyright/data_harness/ux/credentials.py +631 -0
  283. mostlyright/data_harness/ux/diffing.py +444 -0
  284. mostlyright/data_harness/ux/headline.py +671 -0
  285. mostlyright/data_harness/ux/hosted_acquisition.py +974 -0
  286. mostlyright/data_harness/ux/hosted_run_status.py +619 -0
  287. mostlyright/data_harness/ux/inventory.py +427 -0
  288. mostlyright/data_harness/ux/local_review.py +375 -0
  289. mostlyright/data_harness/ux/login.py +691 -0
  290. mostlyright/data_harness/ux/path_kind.py +147 -0
  291. mostlyright/data_harness/ux/peek.py +1000 -0
  292. mostlyright/data_harness/ux/plain_file.py +178 -0
  293. mostlyright/data_harness/ux/plan_check.py +311 -0
  294. mostlyright/data_harness/ux/preflight.py +918 -0
  295. mostlyright/data_harness/ux/readers.py +1124 -0
  296. mostlyright/data_harness/ux/remediation.py +2195 -0
  297. mostlyright/data_harness/ux/render.py +657 -0
  298. mostlyright/data_harness/ux/workload.py +1077 -0
  299. mostlyright/data_harness/viewer.py +3713 -0
  300. mostlyright/data_harness/visual_run/__init__.py +83 -0
  301. mostlyright/data_harness/visual_run/authoring.py +235 -0
  302. mostlyright/data_harness/visual_run/contracts.py +673 -0
  303. mostlyright/data_harness/visual_run/materialize.py +486 -0
  304. mostlyright/data_harness/visual_run/observations.py +874 -0
  305. mostlyright/data_harness/visual_run/query.py +259 -0
  306. mostlyright/data_harness/visual_run/reducer.py +280 -0
  307. mostlyright/data_harness/visual_run/sdk.py +892 -0
  308. mostlyright/data_harness/visual_run/store.py +584 -0
  309. mostlyright/data_harness/visual_run/transport.py +239 -0
  310. mostlyright/data_harness/watch.py +2999 -0
  311. mostlyright_data-0.9.0.dist-info/METADATA +607 -0
  312. mostlyright_data-0.9.0.dist-info/RECORD +314 -0
  313. mostlyright_data-0.9.0.dist-info/WHEEL +4 -0
  314. mostlyright_data-0.9.0.dist-info/entry_points.txt +12 -0
@@ -0,0 +1,523 @@
1
+ """The catalogue release channel: one manifest naming the current generation, and the pin for it.
2
+
3
+ WHY THIS EXISTS. Until ADR 0021 a sealed generation reached a machine exactly one way: an operator
4
+ typed a URL and the archive's SHA-256 next to it (``mr-data catalog-update``). That works for a
5
+ person and not at all for a fleet, and ADR 0021 puts every research lane on a backend worker. A
6
+ worker cannot be typed at. It needs to learn, from its own deploy-owned configuration and from
7
+ nothing else, which generation it is supposed to have -- and it must refuse anything else.
8
+
9
+ THE TWO DOCUMENTS. A *channel manifest* (``channel.json``) is what the channel publishes: it names
10
+ one release archive, its byte count, its digest, and the generation inside it. A *channel pin* is
11
+ what a deployment configures: the channel's address and the exact manifest digest that deployment
12
+ was rolled out with. Neither is trusted on its own. The manifest is bytes off the network and is
13
+ believed only when its SHA-256 equals the pinned one; the archive is believed only when its
14
+ SHA-256 equals the one the believed manifest states; and the generation inside the archive is then
15
+ put through the same ``update.install_release`` verification a hand-typed install goes through.
16
+ There is no new trust root here -- the pin is the root, and it is deploy-owned.
17
+
18
+ WHY THE PIN CARRIES A SELECTION ID. This is deliberately the same shape ``hosted_bootstrap``'s
19
+ ``MOSTLYRIGHT_WORKER_POLICY_SELECTION_JSON`` uses: every field of the selection is folded into one
20
+ identifier that the reader recomputes, so a deployment cannot half-change the pin -- edit the
21
+ digest and leave the URL, say -- without the worker noticing that the selection no longer binds
22
+ the fields it is made of. A drifted pin is refused at start, not tolerated until a query happens
23
+ to hit a missing entry.
24
+
25
+ Nothing in this module performs IO. It is the contract; ``channel_client`` is the fetch.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import hashlib
31
+ import re
32
+ from dataclasses import dataclass
33
+ from typing import Any
34
+ from urllib.parse import urlsplit
35
+
36
+ from mostlyright.data_harness.canonical import (
37
+ CanonicalJSONError,
38
+ canonical_json_bytes,
39
+ parse_canonical_json,
40
+ parse_json,
41
+ )
42
+ from mostlyright.data_harness.sources.catalog.generation_receipt import is_digest
43
+ from mostlyright.data_harness.sources.catalog.update import (
44
+ SUPPORTED_STORE_KINDS,
45
+ ReleaseDescriptor,
46
+ )
47
+ from mostlyright.data_harness.sources.contracts import (
48
+ SourceContractError,
49
+ require_canonical_timestamp,
50
+ )
51
+
52
+ __all__ = [
53
+ "CATALOG_CHANNEL_PIN_ENV",
54
+ "CATALOG_CHANNEL_PIN_SCHEMA",
55
+ "CHANNEL_MANIFEST_OBJECT",
56
+ "CHANNEL_MANIFEST_SCHEMA",
57
+ "MAX_CHANNEL_MANIFEST_BYTES",
58
+ "MAX_CHANNEL_NAME",
59
+ "CatalogChannelPin",
60
+ "ChannelManifest",
61
+ "ChannelRelease",
62
+ "build_channel_manifest",
63
+ "channel_manifest_bytes",
64
+ "parse_catalog_channel_pin",
65
+ "parse_channel_manifest",
66
+ "pin_from_environment",
67
+ "verify_channel_manifest",
68
+ ]
69
+
70
+ #: The manifest's schema token. A reader that does not know this exact string refuses the document
71
+ #: rather than reading the fields it recognises out of it.
72
+ CHANNEL_MANIFEST_SCHEMA = "mr-data-catalog-channel.v1"
73
+
74
+ #: The one object name a channel publishes its manifest under, relative to the channel base.
75
+ CHANNEL_MANIFEST_OBJECT = "channel.json"
76
+
77
+ #: The pin's schema token, and the environment variable a deployment states the pin in. The name
78
+ #: follows ``MOSTLYRIGHT_WORKER_POLICY_SELECTION_JSON`` because it is the same kind of thing: one
79
+ #: deploy-owned JSON document that the worker template supplies and the worker recomputes.
80
+ CATALOG_CHANNEL_PIN_SCHEMA = "mostlyright-worker-catalog-channel.v1"
81
+ CATALOG_CHANNEL_PIN_ENV = "MOSTLYRIGHT_WORKER_CATALOG_CHANNEL_JSON"
82
+
83
+ #: A manifest names one release and nothing else, so it is small by construction. Anything larger
84
+ #: is refused unread rather than parsed and then found to be over-full.
85
+ MAX_CHANNEL_MANIFEST_BYTES = 8_192
86
+
87
+ #: A channel name is an operator's label for one line of successors ("public", "staging").
88
+ MAX_CHANNEL_NAME = 64
89
+
90
+ #: The largest archive a channel may name. Equal to ``update.MAX_RELEASE_ARCHIVE_BYTES``: the
91
+ #: manifest cannot promise bytes the installer would refuse to read.
92
+ MAX_CHANNEL_ARCHIVE_BYTES = 1_073_741_824
93
+
94
+ _CHANNEL_NAME = re.compile(r"[a-z0-9][a-z0-9-]{0,62}[a-z0-9]|[a-z0-9]")
95
+ _ARCHIVE_OBJECT = re.compile(r"[a-z0-9][a-z0-9._-]{0,127}")
96
+ _SELECTION_ID = re.compile(r"[0-9a-f]{64}")
97
+
98
+ _RELEASE_KEYS = frozenset(
99
+ {
100
+ "archive_bytes",
101
+ "archive_object",
102
+ "archive_sha256",
103
+ "entry_count",
104
+ "predecessor_store_sha256",
105
+ "published_at",
106
+ "store_kind",
107
+ "store_sha256",
108
+ }
109
+ )
110
+ _MANIFEST_KEYS = frozenset({"channel", "manifest_id", "release", "schema_version"})
111
+ _PIN_KEYS = frozenset({"channel_url", "manifest_sha256", "schema_version", "selection_id"})
112
+
113
+
114
+ def _refuse(code: str, path: str, detail: str) -> SourceContractError:
115
+ return SourceContractError(code, path, detail)
116
+
117
+
118
+ @dataclass(frozen=True)
119
+ class ChannelRelease:
120
+ """The one release a channel manifest names, and everything needed to fetch and check it."""
121
+
122
+ store_kind: str
123
+ store_sha256: str
124
+ predecessor_store_sha256: str | None
125
+ entry_count: int
126
+ published_at: str
127
+ archive_object: str
128
+ archive_sha256: str
129
+ archive_bytes: int
130
+
131
+ def __post_init__(self) -> None:
132
+ if self.store_kind not in SUPPORTED_STORE_KINDS:
133
+ raise _refuse(
134
+ "CATALOG_CHANNEL_RELEASE",
135
+ "release.store_kind",
136
+ "must name a supported store kind",
137
+ )
138
+ for field in ("store_sha256", "archive_sha256"):
139
+ if not is_digest(getattr(self, field)):
140
+ raise _refuse(
141
+ "CATALOG_CHANNEL_RELEASE",
142
+ f"release.{field}",
143
+ "must be one exact lowercase SHA-256",
144
+ )
145
+ if self.predecessor_store_sha256 is not None:
146
+ if not is_digest(self.predecessor_store_sha256):
147
+ raise _refuse(
148
+ "CATALOG_CHANNEL_RELEASE",
149
+ "release.predecessor_store_sha256",
150
+ "must be one exact lowercase SHA-256 or null",
151
+ )
152
+ if self.predecessor_store_sha256 == self.store_sha256:
153
+ raise _refuse(
154
+ "CATALOG_CHANNEL_RELEASE",
155
+ "release.predecessor_store_sha256",
156
+ "must not name the release itself",
157
+ )
158
+ if _ARCHIVE_OBJECT.fullmatch(self.archive_object) is None:
159
+ raise _refuse(
160
+ "CATALOG_CHANNEL_RELEASE",
161
+ "release.archive_object",
162
+ "must be one plain object name with no path separator",
163
+ )
164
+ for field in ("entry_count", "archive_bytes"):
165
+ value = getattr(self, field)
166
+ if not isinstance(value, int) or isinstance(value, bool) or value < 0:
167
+ raise _refuse(
168
+ "CATALOG_CHANNEL_RELEASE", f"release.{field}", "must be a whole number"
169
+ )
170
+ if not 1 <= self.archive_bytes <= MAX_CHANNEL_ARCHIVE_BYTES:
171
+ raise _refuse(
172
+ "CATALOG_CHANNEL_RELEASE",
173
+ "release.archive_bytes",
174
+ "must be a byte count the installer would accept",
175
+ )
176
+ require_canonical_timestamp(self.published_at, "release.published_at")
177
+
178
+ @classmethod
179
+ def from_dict(cls, data: Any) -> ChannelRelease:
180
+ if not isinstance(data, dict) or set(data) != _RELEASE_KEYS:
181
+ raise _refuse(
182
+ "CATALOG_CHANNEL_RELEASE",
183
+ "release",
184
+ "must carry exactly the declared release fields",
185
+ )
186
+ return cls(
187
+ store_kind=data["store_kind"],
188
+ store_sha256=data["store_sha256"],
189
+ predecessor_store_sha256=data["predecessor_store_sha256"],
190
+ entry_count=data["entry_count"],
191
+ published_at=data["published_at"],
192
+ archive_object=data["archive_object"],
193
+ archive_sha256=data["archive_sha256"],
194
+ archive_bytes=data["archive_bytes"],
195
+ )
196
+
197
+ def to_dict(self) -> dict[str, Any]:
198
+ return {
199
+ "archive_bytes": self.archive_bytes,
200
+ "archive_object": self.archive_object,
201
+ "archive_sha256": self.archive_sha256,
202
+ "entry_count": self.entry_count,
203
+ "predecessor_store_sha256": self.predecessor_store_sha256,
204
+ "published_at": self.published_at,
205
+ "store_kind": self.store_kind,
206
+ "store_sha256": self.store_sha256,
207
+ }
208
+
209
+ def descriptor(self) -> ReleaseDescriptor:
210
+ """The release statement the archive itself must carry, as this manifest states it.
211
+
212
+ The installer re-reads that statement out of the archive and never takes this one for it;
213
+ this is what the manifest *claims*, kept in the manifest's own shape so a mismatch between
214
+ the channel and the bytes is a comparison rather than a surprise.
215
+ """
216
+
217
+ return ReleaseDescriptor(
218
+ store_kind=self.store_kind,
219
+ store_sha256=self.store_sha256,
220
+ predecessor_store_sha256=self.predecessor_store_sha256,
221
+ entry_count=self.entry_count,
222
+ published_at=self.published_at,
223
+ )
224
+
225
+
226
+ @dataclass(frozen=True)
227
+ class ChannelManifest:
228
+ """One channel's statement of its current generation, identified by its own digest."""
229
+
230
+ channel: str
231
+ release: ChannelRelease
232
+ manifest_id: str
233
+
234
+ def __post_init__(self) -> None:
235
+ if not isinstance(self.channel, str) or _CHANNEL_NAME.fullmatch(self.channel) is None:
236
+ raise _refuse(
237
+ "CATALOG_CHANNEL_MANIFEST",
238
+ "channel",
239
+ "must be one lowercase channel name",
240
+ )
241
+ if len(self.channel) > MAX_CHANNEL_NAME:
242
+ raise _refuse("CATALOG_CHANNEL_MANIFEST", "channel", "channel name is too long")
243
+ if not isinstance(self.manifest_id, str) or _SELECTION_ID.fullmatch(self.manifest_id) is (
244
+ None
245
+ ):
246
+ raise _refuse(
247
+ "CATALOG_CHANNEL_MANIFEST",
248
+ "manifest_id",
249
+ "must be one exact lowercase SHA-256",
250
+ )
251
+ if self.manifest_id != _manifest_identity(self.channel, self.release):
252
+ raise _refuse(
253
+ "CATALOG_CHANNEL_MANIFEST",
254
+ "manifest_id",
255
+ "does not bind the exact channel and release this manifest states",
256
+ )
257
+
258
+ def to_dict(self) -> dict[str, Any]:
259
+ return {
260
+ "channel": self.channel,
261
+ "manifest_id": self.manifest_id,
262
+ "release": self.release.to_dict(),
263
+ "schema_version": CHANNEL_MANIFEST_SCHEMA,
264
+ }
265
+
266
+
267
+ def _manifest_identity(channel: str, release: ChannelRelease) -> str:
268
+ """The manifest's identity: its own digest over everything it says except the identity."""
269
+
270
+ unsigned = {
271
+ "channel": channel,
272
+ "release": release.to_dict(),
273
+ "schema_version": CHANNEL_MANIFEST_SCHEMA,
274
+ }
275
+ return hashlib.sha256(canonical_json_bytes(unsigned)).hexdigest()
276
+
277
+
278
+ def parse_channel_manifest(data: Any) -> ChannelManifest:
279
+ """Parse one channel manifest from already-decoded JSON, recomputing its identity."""
280
+
281
+ if not isinstance(data, dict) or set(data) != _MANIFEST_KEYS:
282
+ raise _refuse(
283
+ "CATALOG_CHANNEL_MANIFEST",
284
+ "channel",
285
+ "must carry exactly the declared channel manifest fields",
286
+ )
287
+ if data["schema_version"] != CHANNEL_MANIFEST_SCHEMA:
288
+ raise _refuse(
289
+ "CATALOG_CHANNEL_MANIFEST",
290
+ "channel.schema_version",
291
+ f"must be {CHANNEL_MANIFEST_SCHEMA}",
292
+ )
293
+ return ChannelManifest(
294
+ channel=data["channel"],
295
+ release=ChannelRelease.from_dict(data["release"]),
296
+ manifest_id=data["manifest_id"],
297
+ )
298
+
299
+
300
+ def build_channel_manifest(
301
+ *,
302
+ channel: str,
303
+ store_kind: str,
304
+ store_sha256: str,
305
+ predecessor_store_sha256: str | None,
306
+ entry_count: int,
307
+ published_at: str,
308
+ archive_object: str,
309
+ archive_sha256: str,
310
+ archive_bytes: int,
311
+ ) -> ChannelManifest:
312
+ """Build the manifest for one already-packaged release. Publishing side only."""
313
+
314
+ release = ChannelRelease(
315
+ store_kind=store_kind,
316
+ store_sha256=store_sha256,
317
+ predecessor_store_sha256=predecessor_store_sha256,
318
+ entry_count=entry_count,
319
+ published_at=published_at,
320
+ archive_object=archive_object,
321
+ archive_sha256=archive_sha256,
322
+ archive_bytes=archive_bytes,
323
+ )
324
+ return ChannelManifest(
325
+ channel=channel, release=release, manifest_id=_manifest_identity(channel, release)
326
+ )
327
+
328
+
329
+ def channel_manifest_bytes(manifest: ChannelManifest) -> bytes:
330
+ """The exact bytes to publish as ``channel.json``. Canonical, so the digest is reproducible."""
331
+
332
+ raw = canonical_json_bytes(manifest.to_dict())
333
+ if len(raw) > MAX_CHANNEL_MANIFEST_BYTES: # pragma: no cover - the shape is closed and small
334
+ raise _refuse(
335
+ "CATALOG_CHANNEL_MANIFEST", "channel", "channel manifest is larger than the bound"
336
+ )
337
+ return raw
338
+
339
+
340
+ def verify_channel_manifest(raw: bytes, *, expected_manifest_sha256: str) -> ChannelManifest:
341
+ """Believe one channel manifest only when its bytes are the pinned bytes.
342
+
343
+ The digest is checked *before* the JSON is parsed, so a manifest that is not the pinned one
344
+ never reaches the parser at all. That ordering is the whole point: parsing is the only part of
345
+ this path with any surface, and unpinned bytes must not get to use it.
346
+ """
347
+
348
+ if not isinstance(raw, bytes):
349
+ raise _refuse("CATALOG_CHANNEL_MANIFEST", "channel", "manifest must be bytes")
350
+ if not is_digest(expected_manifest_sha256):
351
+ raise _refuse(
352
+ "CATALOG_CHANNEL_DIGEST_SPELLING",
353
+ "expected_manifest_sha256",
354
+ "must be exactly 64 lowercase hex characters",
355
+ )
356
+ if len(raw) > MAX_CHANNEL_MANIFEST_BYTES:
357
+ raise _refuse(
358
+ "CATALOG_CHANNEL_MANIFEST", "channel", "channel manifest is larger than the bound"
359
+ )
360
+ if hashlib.sha256(raw).hexdigest() != expected_manifest_sha256:
361
+ raise _refuse(
362
+ "CATALOG_CHANNEL_DIGEST",
363
+ "channel",
364
+ "channel manifest bytes do not match the pinned SHA-256",
365
+ )
366
+ try:
367
+ data = parse_canonical_json(raw)
368
+ except CanonicalJSONError as error:
369
+ raise _refuse(
370
+ "CATALOG_CHANNEL_MANIFEST", "channel", "channel manifest is not canonical JSON"
371
+ ) from error
372
+ return parse_channel_manifest(data)
373
+
374
+
375
+ @dataclass(frozen=True)
376
+ class CatalogChannelPin:
377
+ """What one deployment declares about the catalogue it is supposed to be running.
378
+
379
+ Deliberately the same shape as ``hosted_bootstrap.WorkerPolicyBinding``: every field folded
380
+ into one recomputed ``selection_id``, so a partially-edited pin is a refusal rather than a
381
+ half-applied rollout.
382
+ """
383
+
384
+ channel_url: str
385
+ manifest_sha256: str
386
+ selection_id: str
387
+
388
+ def __post_init__(self) -> None:
389
+ _require_channel_url(self.channel_url)
390
+ if not is_digest(self.manifest_sha256):
391
+ raise _refuse(
392
+ "CATALOG_CHANNEL_PIN",
393
+ "catalog_channel.manifest_sha256",
394
+ "must be one exact lowercase SHA-256",
395
+ )
396
+ if (
397
+ not isinstance(self.selection_id, str)
398
+ or _SELECTION_ID.fullmatch(self.selection_id) is None
399
+ ):
400
+ raise _refuse(
401
+ "CATALOG_CHANNEL_PIN",
402
+ "catalog_channel.selection_id",
403
+ "must be one exact lowercase SHA-256",
404
+ )
405
+ if self.selection_id != _pin_identity(self.channel_url, self.manifest_sha256):
406
+ raise _refuse(
407
+ "CATALOG_CHANNEL_PIN",
408
+ "catalog_channel.selection_id",
409
+ "does not bind the exact channel address and manifest digest pinned here",
410
+ )
411
+
412
+ def to_dict(self) -> dict[str, Any]:
413
+ return {
414
+ "channel_url": self.channel_url,
415
+ "manifest_sha256": self.manifest_sha256,
416
+ "schema_version": CATALOG_CHANNEL_PIN_SCHEMA,
417
+ "selection_id": self.selection_id,
418
+ }
419
+
420
+
421
+ def _pin_identity(channel_url: str, manifest_sha256: str) -> str:
422
+ unsigned = {
423
+ "channel_url": channel_url,
424
+ "manifest_sha256": manifest_sha256,
425
+ "schema_version": CATALOG_CHANNEL_PIN_SCHEMA,
426
+ }
427
+ return hashlib.sha256(canonical_json_bytes(unsigned)).hexdigest()
428
+
429
+
430
+ def _require_channel_url(value: Any) -> str:
431
+ """A channel address is one plain HTTPS URL ending in the manifest object, and nothing else.
432
+
433
+ No query, no fragment, no user information. A channel is a published location, not a signed
434
+ capability: if a deployment needs authorisation to read it, that travels in a header the
435
+ fetcher adds, never in the address a pin is compared by.
436
+ """
437
+
438
+ if not isinstance(value, str) or not 1 <= len(value) <= 2048:
439
+ raise _refuse("CATALOG_CHANNEL_PIN", "catalog_channel.channel_url", "is not a usable URL")
440
+ parsed = urlsplit(value)
441
+ if (
442
+ parsed.scheme != "https"
443
+ or not parsed.hostname
444
+ or parsed.username is not None
445
+ or parsed.password is not None
446
+ or parsed.query
447
+ or parsed.fragment
448
+ or not parsed.path.endswith("/" + CHANNEL_MANIFEST_OBJECT)
449
+ ):
450
+ raise _refuse(
451
+ "CATALOG_CHANNEL_PIN",
452
+ "catalog_channel.channel_url",
453
+ "must be one https address, without user information, query or fragment, "
454
+ f"whose path ends in /{CHANNEL_MANIFEST_OBJECT}",
455
+ )
456
+ return value
457
+
458
+
459
+ def parse_catalog_channel_pin(data: Any) -> CatalogChannelPin:
460
+ """Parse the deploy-owned pin, recomputing the selection identity it states."""
461
+
462
+ if not isinstance(data, dict) or set(data) != _PIN_KEYS:
463
+ raise _refuse(
464
+ "CATALOG_CHANNEL_PIN",
465
+ "catalog_channel",
466
+ "must carry exactly the declared catalogue channel pin fields",
467
+ )
468
+ if data["schema_version"] != CATALOG_CHANNEL_PIN_SCHEMA:
469
+ raise _refuse(
470
+ "CATALOG_CHANNEL_PIN",
471
+ "catalog_channel.schema_version",
472
+ f"must be {CATALOG_CHANNEL_PIN_SCHEMA}",
473
+ )
474
+ return CatalogChannelPin(
475
+ channel_url=data["channel_url"],
476
+ manifest_sha256=data["manifest_sha256"],
477
+ selection_id=data["selection_id"],
478
+ )
479
+
480
+
481
+ def build_catalog_channel_pin(*, channel_url: str, manifest_sha256: str) -> CatalogChannelPin:
482
+ """Build the pin a deployment should be configured with. Publishing side only."""
483
+
484
+ _require_channel_url(channel_url)
485
+ if not is_digest(manifest_sha256):
486
+ raise _refuse(
487
+ "CATALOG_CHANNEL_PIN",
488
+ "catalog_channel.manifest_sha256",
489
+ "must be one exact lowercase SHA-256",
490
+ )
491
+ return CatalogChannelPin(
492
+ channel_url=channel_url,
493
+ manifest_sha256=manifest_sha256,
494
+ selection_id=_pin_identity(channel_url, manifest_sha256),
495
+ )
496
+
497
+
498
+ def pin_from_environment(environment: Any) -> CatalogChannelPin | None:
499
+ """Read the pin out of a worker's environment, or answer None when none is configured.
500
+
501
+ None means "this deployment does not host a catalogue", which is a legitimate configuration
502
+ and the one every deployment has before the channel is rolled out to it. It is emphatically
503
+ not the same as a *malformed* pin, which is a refusal: a deployment that tried to say
504
+ something about its catalogue and said it wrongly must not start as though it had said
505
+ nothing.
506
+ """
507
+
508
+ raw = environment.get(CATALOG_CHANNEL_PIN_ENV)
509
+ if raw is None:
510
+ return None
511
+ if not isinstance(raw, str) or not raw.strip():
512
+ raise _refuse(
513
+ "CATALOG_CHANNEL_PIN",
514
+ CATALOG_CHANNEL_PIN_ENV,
515
+ "is present but empty; unset it to run without a hosted catalogue",
516
+ )
517
+ try:
518
+ data = parse_json(raw)
519
+ except CanonicalJSONError as error:
520
+ raise _refuse(
521
+ "CATALOG_CHANNEL_PIN", CATALOG_CHANNEL_PIN_ENV, "is not one JSON object"
522
+ ) from error
523
+ return parse_catalog_channel_pin(data)