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,524 @@
1
+ """Bounded stream-batch adapter with durable replay-safe watermark evidence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import fcntl
7
+ import os
8
+ import re
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+ from typing import Any, Protocol
12
+
13
+ from mostlyright.data_harness.acquisition.http import seal_content_addressed_snapshot
14
+ from mostlyright.data_harness.acquisition.sandbox import CrawlerSandbox
15
+ from mostlyright.data_harness.canonical import (
16
+ MAX_CANONICAL_MEMBERS,
17
+ CanonicalJSONError,
18
+ canonical_json_bytes,
19
+ canonical_sha256,
20
+ parse_json,
21
+ sha256_bytes,
22
+ )
23
+ from mostlyright.data_harness.sources._adapter_steps import (
24
+ _authorize_candidate,
25
+ _output,
26
+ _parse_limits,
27
+ _require_sandbox_binding,
28
+ )
29
+ from mostlyright.data_harness.sources.adapters import AdapterGovernance
30
+ from mostlyright.data_harness.sources.contracts import (
31
+ AcquisitionRequest,
32
+ AdapterDescriptor,
33
+ SourceContractError,
34
+ )
35
+ from mostlyright.data_harness.sources.governance import (
36
+ classify_before_persistence,
37
+ require_admissible,
38
+ )
39
+ from mostlyright.data_harness.sources.registry import AcquisitionContext, AcquisitionOutput
40
+
41
+ _STATE_VERSION = "harness-stream-watermark.v1"
42
+ # The exact coordinate a Recipe pins and a registry resolves. Named once here so a caller
43
+ # that has to recognise a stream source reads the adapter's own identity rather than
44
+ # repeating the string.
45
+ STREAM_BATCH_ADAPTER_ID = "user.stream-batch"
46
+ STREAM_BATCH_ADAPTER_VERSION = "1.0.0"
47
+ # The one grammar an event name has to be in to enter the ledger. Named once, here, because the
48
+ # Recorder that assigns names and the ledger that admits them must not drift apart.
49
+ EVENT_ID_GRAMMAR = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$")
50
+ _EVENT_ID = EVENT_ID_GRAMMAR
51
+ MAX_BATCH_EVENTS = 100_000
52
+ MAX_STATE_BYTES = 64 * 1024 * 1024
53
+ # The state object itself contributes eight canonical members. Keeping the ledger
54
+ # inside the same parser budget is required in addition to the serialized byte cap.
55
+ MAX_SEEN_EVENTS = MAX_CANONICAL_MEMBERS - 8
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class StreamBatch:
60
+ """One bounded provider batch from a StreamGateway, with the watermark it advances to."""
61
+
62
+ query_digest: str
63
+ previous_watermark: str | None
64
+ next_watermark: str
65
+ provider_event_ids: tuple[str, ...]
66
+ content: bytes
67
+ content_sha256: str
68
+ media_type: str
69
+ data_format: str
70
+ filename: str
71
+ observed_at: str
72
+ available_at: str
73
+
74
+ def __post_init__(self) -> None:
75
+ if sha256_bytes(self.content) != self.content_sha256:
76
+ raise SourceContractError(
77
+ "STREAM_CONTENT_DIGEST",
78
+ "stream_batch.content_sha256",
79
+ "does not match exact batch bytes",
80
+ )
81
+ if not self.provider_event_ids or len(self.provider_event_ids) > MAX_BATCH_EVENTS:
82
+ raise SourceContractError(
83
+ "STREAM_EVENT_LIMIT",
84
+ "stream_batch.provider_event_ids",
85
+ "must be non-empty and bounded",
86
+ )
87
+ if len(set(self.provider_event_ids)) != len(self.provider_event_ids) or any(
88
+ _EVENT_ID.fullmatch(value) is None for value in self.provider_event_ids
89
+ ):
90
+ raise SourceContractError(
91
+ "STREAM_EVENT_ID",
92
+ "stream_batch.provider_event_ids",
93
+ "provider event IDs must be valid and unique",
94
+ )
95
+ if not self.next_watermark or self.next_watermark == self.previous_watermark:
96
+ raise SourceContractError(
97
+ "STREAM_WATERMARK",
98
+ "stream_batch.next_watermark",
99
+ "must advance to a non-empty different value",
100
+ )
101
+ for name, value in (
102
+ ("previous_watermark", self.previous_watermark),
103
+ ("next_watermark", self.next_watermark),
104
+ ):
105
+ if value is not None and (
106
+ not isinstance(value, str)
107
+ or not value
108
+ or len(value) > 512
109
+ or any(0xD800 <= ord(char) <= 0xDFFF for char in value)
110
+ ):
111
+ raise SourceContractError(
112
+ "STREAM_WATERMARK",
113
+ f"stream_batch.{name}",
114
+ "must be a bounded valid string or null",
115
+ )
116
+ if self.data_format not in {"json", "ndjson"}:
117
+ raise SourceContractError(
118
+ "STREAM_FORMAT",
119
+ "stream_batch.data_format",
120
+ "must be json or ndjson",
121
+ )
122
+
123
+
124
+ class StreamGateway(Protocol):
125
+ def read_batch(self, request: AcquisitionRequest) -> StreamBatch:
126
+ """Return one coordinator-bounded provider batch."""
127
+
128
+
129
+ @dataclass(frozen=True)
130
+ class StreamState:
131
+ """Durable per-source watermark and admitted event-ID ledger; StreamWatermarkStore writes it."""
132
+
133
+ source_id: str
134
+ watermark: str
135
+ seen_event_ids: tuple[str, ...]
136
+ last_batch_content_sha256: str
137
+ last_batch_event_ids_digest: str
138
+ last_query_digest: str
139
+ updated_at: str
140
+ schema_version: str = _STATE_VERSION
141
+
142
+ def __post_init__(self) -> None:
143
+ if self.schema_version != _STATE_VERSION:
144
+ raise SourceContractError(
145
+ "STREAM_STATE_VERSION",
146
+ "stream_state.schema_version",
147
+ "is unsupported",
148
+ )
149
+ if not re.fullmatch(r"[a-z][a-z0-9._-]{0,127}", self.source_id):
150
+ raise SourceContractError("STREAM_STATE", "stream_state.source_id", "is invalid")
151
+ if not self.watermark or len(self.watermark) > 512:
152
+ raise SourceContractError("STREAM_STATE", "stream_state.watermark", "is invalid")
153
+ if (
154
+ len(self.seen_event_ids) > MAX_SEEN_EVENTS
155
+ or len(set(self.seen_event_ids)) != len(self.seen_event_ids)
156
+ or tuple(sorted(self.seen_event_ids)) != self.seen_event_ids
157
+ or any(_EVENT_ID.fullmatch(value) is None for value in self.seen_event_ids)
158
+ ):
159
+ raise SourceContractError(
160
+ "STREAM_STATE",
161
+ "stream_state.seen_event_ids",
162
+ "must be a bounded canonical unique event-ID ledger",
163
+ )
164
+ for name, value in (
165
+ ("last_batch_content_sha256", self.last_batch_content_sha256),
166
+ ("last_batch_event_ids_digest", self.last_batch_event_ids_digest),
167
+ ("last_query_digest", self.last_query_digest),
168
+ ):
169
+ if len(value) != 64 or any(char not in "0123456789abcdef" for char in value):
170
+ raise SourceContractError("STREAM_STATE", f"stream_state.{name}", "is invalid")
171
+
172
+ @property
173
+ def digest(self) -> str:
174
+ return canonical_sha256(self.to_dict())
175
+
176
+ def to_dict(self) -> dict[str, Any]:
177
+ return {
178
+ "schema_version": self.schema_version,
179
+ "source_id": self.source_id,
180
+ "watermark": self.watermark,
181
+ "seen_event_ids": list(self.seen_event_ids),
182
+ "last_batch_content_sha256": self.last_batch_content_sha256,
183
+ "last_batch_event_ids_digest": self.last_batch_event_ids_digest,
184
+ "last_query_digest": self.last_query_digest,
185
+ "updated_at": self.updated_at,
186
+ }
187
+
188
+
189
+ @dataclass(frozen=True)
190
+ class StreamWatermarkStore:
191
+ root: Path
192
+
193
+ def __post_init__(self) -> None:
194
+ if not self.root.is_absolute() or self.root.is_symlink() or not self.root.is_dir():
195
+ raise SourceContractError(
196
+ "STREAM_STATE_ROOT",
197
+ "watermark_store.root",
198
+ "must be an existing absolute non-symlink directory",
199
+ )
200
+
201
+ def commit(
202
+ self,
203
+ *,
204
+ source_id: str,
205
+ batch: StreamBatch,
206
+ updated_at: str,
207
+ ) -> StreamState:
208
+ if not re.fullmatch(r"[a-z][a-z0-9._-]{0,127}", source_id):
209
+ raise SourceContractError("STREAM_SOURCE_ID", "source_id", "is invalid")
210
+ root_fd = os.open(
211
+ self.root,
212
+ os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0),
213
+ )
214
+ state_name = f"watermark-{source_id}.json"
215
+ try:
216
+ fcntl.flock(root_fd, fcntl.LOCK_EX)
217
+ current = _load_state(root_fd, state_name)
218
+ event_digest = canonical_sha256(list(batch.provider_event_ids))
219
+ if current is not None and (
220
+ current.watermark == batch.next_watermark
221
+ and current.last_batch_content_sha256 == batch.content_sha256
222
+ and current.last_batch_event_ids_digest == event_digest
223
+ and current.last_query_digest == batch.query_digest
224
+ ):
225
+ return current
226
+ current_watermark = current.watermark if current is not None else None
227
+ if batch.previous_watermark != current_watermark:
228
+ raise SourceContractError(
229
+ "STREAM_WATERMARK_CONFLICT",
230
+ "stream_batch.previous_watermark",
231
+ "does not match durable coordinator state",
232
+ )
233
+ seen = set(current.seen_event_ids if current is not None else ())
234
+ duplicates = sorted(seen & set(batch.provider_event_ids))
235
+ if duplicates:
236
+ raise SourceContractError(
237
+ "STREAM_EVENT_REPLAY",
238
+ "stream_batch.provider_event_ids",
239
+ f"provider event IDs were already admitted: {duplicates[:10]}",
240
+ )
241
+ combined = tuple(sorted((*seen, *batch.provider_event_ids)))
242
+ if len(combined) > MAX_SEEN_EVENTS:
243
+ raise SourceContractError(
244
+ "STREAM_SEEN_EVENT_LIMIT",
245
+ "stream_state.seen_event_ids",
246
+ "durable event-ID ledger reached its V1 bound",
247
+ )
248
+ successor = StreamState(
249
+ source_id=source_id,
250
+ watermark=batch.next_watermark,
251
+ seen_event_ids=combined,
252
+ last_batch_content_sha256=batch.content_sha256,
253
+ last_batch_event_ids_digest=event_digest,
254
+ last_query_digest=batch.query_digest,
255
+ updated_at=updated_at,
256
+ )
257
+ _atomic_state_replace(root_fd, state_name, successor)
258
+ return successor
259
+ finally:
260
+ with contextlib.suppress(OSError):
261
+ fcntl.flock(root_fd, fcntl.LOCK_UN)
262
+ os.close(root_fd)
263
+
264
+
265
+ def _stream_descriptor() -> AdapterDescriptor:
266
+ return AdapterDescriptor(
267
+ adapter_id=STREAM_BATCH_ADAPTER_ID,
268
+ adapter_version=STREAM_BATCH_ADAPTER_VERSION,
269
+ source_class="stream",
270
+ data_formats=("json", "ndjson"),
271
+ capabilities=("snapshot", "stream", "refresh"),
272
+ )
273
+
274
+
275
+ @dataclass(frozen=True)
276
+ class StreamBatchAdapter:
277
+ gateway: StreamGateway
278
+ sandbox: CrawlerSandbox
279
+ governance: AdapterGovernance
280
+ watermark_store: StreamWatermarkStore
281
+ descriptor: AdapterDescriptor = field(default_factory=_stream_descriptor)
282
+
283
+ def acquire(
284
+ self,
285
+ request: AcquisitionRequest,
286
+ context: AcquisitionContext,
287
+ ) -> AcquisitionOutput:
288
+ if (request.adapter_id, request.adapter_version) != (
289
+ self.descriptor.adapter_id,
290
+ self.descriptor.adapter_version,
291
+ ):
292
+ raise SourceContractError(
293
+ "ADAPTER_BINDING",
294
+ "request",
295
+ "request does not bind the exact stream adapter",
296
+ )
297
+ if request.credential_reference_id is not None:
298
+ raise SourceContractError(
299
+ "STREAM_CREDENTIAL_FORBIDDEN",
300
+ "request.credential_reference_id",
301
+ "stream gateway credentials remain in the trusted gateway",
302
+ )
303
+ _stream_query(request.query)
304
+ _authorize_candidate(request, self.governance)
305
+ batch = self.gateway.read_batch(request)
306
+ if batch.query_digest != request.query_digest:
307
+ raise SourceContractError(
308
+ "STREAM_QUERY_BINDING",
309
+ "stream_batch.query_digest",
310
+ "batch does not bind the exact canonical query",
311
+ )
312
+ if (
313
+ request.query["expected_watermark"] != batch.previous_watermark
314
+ or request.query["data_format"] != batch.data_format
315
+ or request.query["filename"] != batch.filename
316
+ ):
317
+ raise SourceContractError(
318
+ "STREAM_QUERY_BINDING",
319
+ "stream_batch",
320
+ "batch watermark, format, or filename differs from the exact request",
321
+ )
322
+ if (
323
+ batch.observed_at != self.governance.observation.observed_at
324
+ or batch.available_at != self.governance.observation.available_at
325
+ ):
326
+ raise SourceContractError(
327
+ "STREAM_TEMPORAL_BINDING",
328
+ "stream_batch",
329
+ "batch times differ from the recorded observation evidence",
330
+ )
331
+ if batch.data_format not in self.descriptor.data_formats:
332
+ raise SourceContractError(
333
+ "STREAM_FORMAT",
334
+ "stream_batch.data_format",
335
+ "batch format is not supported by the adapter",
336
+ )
337
+ classification = classify_before_persistence(
338
+ batch.content,
339
+ declared_classification=self.governance.declared_classification,
340
+ lawful_basis_declared=self.governance.lawful_basis_declared,
341
+ )
342
+ require_admissible(classification)
343
+ parsed = self.sandbox.parse(
344
+ request_id=request.request_id,
345
+ content=batch.content,
346
+ data_format=batch.data_format,
347
+ media_type=batch.media_type,
348
+ filename=batch.filename,
349
+ limits=_parse_limits(context),
350
+ )
351
+ _require_sandbox_binding(parsed.policy_digest, context)
352
+ if parsed.parsed is None or parsed.parsed.input_sha256 != batch.content_sha256:
353
+ raise SourceContractError(
354
+ "STREAM_CONTENT_BINDING",
355
+ "sandbox.parsed",
356
+ "parsed result does not bind the exact stream bytes",
357
+ )
358
+ snapshot_path = seal_content_addressed_snapshot(
359
+ context.snapshot_root,
360
+ content=batch.content,
361
+ suffix=batch.data_format,
362
+ )
363
+ state = self.watermark_store.commit(
364
+ source_id=request.source_id,
365
+ batch=batch,
366
+ updated_at=request.requested_at,
367
+ )
368
+ content_urn = f"urn:sha256:{batch.content_sha256}"
369
+ return _output(
370
+ request=request,
371
+ descriptor=self.descriptor,
372
+ governance=self.governance,
373
+ snapshot_path=snapshot_path,
374
+ content=batch.content,
375
+ data_format=batch.data_format,
376
+ media_type=batch.media_type,
377
+ source_uri=content_urn,
378
+ final_uri=content_urn,
379
+ classification=classification.classification,
380
+ transport_evidence_digest=state.digest,
381
+ parsed_schema_digest=parsed.parsed.schema_digest,
382
+ next_watermark=state.watermark,
383
+ )
384
+
385
+
386
+ def _stream_query(value: Any) -> None:
387
+ if not isinstance(value, dict) or set(value) != {
388
+ "stream_id",
389
+ "data_format",
390
+ "filename",
391
+ "expected_watermark",
392
+ }:
393
+ raise SourceContractError(
394
+ "STREAM_QUERY",
395
+ "request.query",
396
+ "must contain the exact stream batch query fields",
397
+ )
398
+ if value["data_format"] not in {"json", "ndjson"}:
399
+ raise SourceContractError(
400
+ "STREAM_QUERY",
401
+ "request.query.data_format",
402
+ "must be json or ndjson",
403
+ )
404
+ if value["expected_watermark"] is not None and not isinstance(value["expected_watermark"], str):
405
+ raise SourceContractError(
406
+ "STREAM_QUERY",
407
+ "request.query.expected_watermark",
408
+ "must be a string or null",
409
+ )
410
+ for name in ("stream_id", "filename"):
411
+ if not isinstance(value[name], str) or not value[name] or len(value[name]) > 512:
412
+ raise SourceContractError(
413
+ "STREAM_QUERY",
414
+ f"request.query.{name}",
415
+ "must be a non-empty bounded string",
416
+ )
417
+
418
+
419
+ def _load_state(root_fd: int, name: str) -> StreamState | None:
420
+ try:
421
+ descriptor = os.open(
422
+ name,
423
+ os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0),
424
+ dir_fd=root_fd,
425
+ )
426
+ except FileNotFoundError:
427
+ return None
428
+ try:
429
+ raw = _read_bounded(descriptor, MAX_STATE_BYTES)
430
+ finally:
431
+ os.close(descriptor)
432
+ value = parse_json(raw)
433
+ if not isinstance(value, dict) or set(value) != {
434
+ "schema_version",
435
+ "source_id",
436
+ "watermark",
437
+ "seen_event_ids",
438
+ "last_batch_content_sha256",
439
+ "last_batch_event_ids_digest",
440
+ "last_query_digest",
441
+ "updated_at",
442
+ }:
443
+ raise SourceContractError("STREAM_STATE", "watermark_state", "shape is invalid")
444
+ if value["schema_version"] != _STATE_VERSION or not isinstance(value["seen_event_ids"], list):
445
+ raise SourceContractError("STREAM_STATE", "watermark_state", "version is invalid")
446
+ return StreamState(
447
+ source_id=value["source_id"],
448
+ watermark=value["watermark"],
449
+ seen_event_ids=tuple(value["seen_event_ids"]),
450
+ last_batch_content_sha256=value["last_batch_content_sha256"],
451
+ last_batch_event_ids_digest=value["last_batch_event_ids_digest"],
452
+ last_query_digest=value["last_query_digest"],
453
+ updated_at=value["updated_at"],
454
+ )
455
+
456
+
457
+ def _atomic_state_replace(root_fd: int, name: str, state: StreamState) -> None:
458
+ payload = _serialize_state(state)
459
+ temporary = f".{name}.{os.getpid()}.tmp"
460
+ with contextlib.suppress(FileNotFoundError):
461
+ os.unlink(temporary, dir_fd=root_fd)
462
+ descriptor = os.open(
463
+ temporary,
464
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0),
465
+ 0o600,
466
+ dir_fd=root_fd,
467
+ )
468
+ try:
469
+ view = memoryview(payload)
470
+ written = 0
471
+ while written < len(view):
472
+ count = os.write(descriptor, view[written:])
473
+ if count <= 0:
474
+ raise SourceContractError(
475
+ "STREAM_STATE_WRITE",
476
+ "watermark_state",
477
+ "write made no progress",
478
+ )
479
+ written += count
480
+ os.fsync(descriptor)
481
+ finally:
482
+ os.close(descriptor)
483
+ try:
484
+ os.replace(temporary, name, src_dir_fd=root_fd, dst_dir_fd=root_fd)
485
+ os.fsync(root_fd)
486
+ finally:
487
+ with contextlib.suppress(FileNotFoundError):
488
+ os.unlink(temporary, dir_fd=root_fd)
489
+
490
+
491
+ def _serialize_state(state: StreamState) -> bytes:
492
+ try:
493
+ payload = canonical_json_bytes(state.to_dict())
494
+ except CanonicalJSONError as error:
495
+ raise SourceContractError(
496
+ "STREAM_STATE_LIMIT",
497
+ "stream_state.seen_event_ids",
498
+ "state exceeds the canonical parser member budget",
499
+ ) from error
500
+ if len(payload) > MAX_STATE_BYTES:
501
+ raise SourceContractError(
502
+ "STREAM_STATE_LIMIT",
503
+ "watermark_state",
504
+ f"canonical state exceeds its {MAX_STATE_BYTES}-byte durable loader budget",
505
+ )
506
+ return payload
507
+
508
+
509
+ def _read_bounded(descriptor: int, maximum: int) -> bytes:
510
+ chunks: list[bytes] = []
511
+ total = 0
512
+ while total <= maximum:
513
+ chunk = os.read(descriptor, min(64 * 1024, maximum + 1 - total))
514
+ if not chunk:
515
+ break
516
+ chunks.append(chunk)
517
+ total += len(chunk)
518
+ if total > maximum:
519
+ raise SourceContractError(
520
+ "STREAM_STATE_LIMIT",
521
+ "watermark_state",
522
+ "state exceeds its byte budget",
523
+ )
524
+ return b"".join(chunks)