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,218 @@
1
+ """Strict source-independent integration seam for acquired tabular snapshots."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ from mostlyright.data_harness.acquisition.parsing import ParsedTable
9
+ from mostlyright.data_harness.canonical import canonical_sha256, sha256_bytes
10
+ from mostlyright.data_harness.sources.contracts import SourceContractError
11
+ from mostlyright.data_harness.sources.registry import AcquisitionOutput
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class OneToOneJoinEvidence:
16
+ left_input_sha256: str
17
+ right_input_sha256: str
18
+ join_key: str
19
+ left_rows: int
20
+ right_rows: int
21
+ output_rows: int
22
+ unmatched_left_rows: int
23
+ row_multiplier: float
24
+ output_columns: tuple[str, ...]
25
+ output_rows_value: tuple[tuple[Any, ...], ...]
26
+
27
+ @property
28
+ def digest(self) -> str:
29
+ return canonical_sha256(
30
+ {
31
+ "left_input_sha256": self.left_input_sha256,
32
+ "right_input_sha256": self.right_input_sha256,
33
+ "join_key": self.join_key,
34
+ "left_rows": self.left_rows,
35
+ "right_rows": self.right_rows,
36
+ "output_rows": self.output_rows,
37
+ "unmatched_left_rows": self.unmatched_left_rows,
38
+ "row_multiplier": {
39
+ "numerator": self.output_rows,
40
+ "denominator": self.left_rows,
41
+ },
42
+ "output_columns": list(self.output_columns),
43
+ "output_rows_value": [list(row) for row in self.output_rows_value],
44
+ }
45
+ )
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class AcquiredOneToOneJoinEvidence:
50
+ """A one-to-one join bound to both exact acquisition receipts and observations."""
51
+
52
+ left_receipt_digest: str
53
+ right_receipt_digest: str
54
+ left_observation_digest: str
55
+ right_observation_digest: str
56
+ join: OneToOneJoinEvidence
57
+
58
+ @property
59
+ def digest(self) -> str:
60
+ return canonical_sha256(
61
+ {
62
+ "left_receipt_digest": self.left_receipt_digest,
63
+ "right_receipt_digest": self.right_receipt_digest,
64
+ "left_observation_digest": self.left_observation_digest,
65
+ "right_observation_digest": self.right_observation_digest,
66
+ "join_digest": self.join.digest,
67
+ }
68
+ )
69
+
70
+
71
+ def join_acquired_sources_one_to_one(
72
+ left_output: AcquisitionOutput,
73
+ left_table: ParsedTable,
74
+ right_output: AcquisitionOutput,
75
+ right_table: ParsedTable,
76
+ *,
77
+ join_key: str,
78
+ ) -> AcquiredOneToOneJoinEvidence:
79
+ """Join only tables proven to be parsed from their exact admitted snapshots."""
80
+
81
+ _require_acquired_table_binding(left_output, left_table, "left")
82
+ _require_acquired_table_binding(right_output, right_table, "right")
83
+ return AcquiredOneToOneJoinEvidence(
84
+ left_receipt_digest=left_output.receipt.digest,
85
+ right_receipt_digest=right_output.receipt.digest,
86
+ left_observation_digest=left_output.observation.digest,
87
+ right_observation_digest=right_output.observation.digest,
88
+ join=join_source_tables_one_to_one(left_table, right_table, join_key=join_key),
89
+ )
90
+
91
+
92
+ def join_source_tables_one_to_one(
93
+ left: ParsedTable,
94
+ right: ParsedTable,
95
+ *,
96
+ join_key: str,
97
+ ) -> OneToOneJoinEvidence:
98
+ """Join two acquired tables only when the exact key relation is one-to-one and complete."""
99
+
100
+ if not isinstance(join_key, str) or not join_key:
101
+ raise SourceContractError("JOIN_KEY", "join_key", "must be non-empty text")
102
+ try:
103
+ left_key_index = left.columns.index(join_key)
104
+ right_key_index = right.columns.index(join_key)
105
+ except ValueError:
106
+ raise SourceContractError(
107
+ "JOIN_KEY",
108
+ "join_key",
109
+ "must exist in both acquired tables",
110
+ ) from None
111
+ right_extra_columns = tuple(column for column in right.columns if column != join_key)
112
+ overlaps = sorted(set(left.columns) & set(right_extra_columns))
113
+ if overlaps:
114
+ raise SourceContractError(
115
+ "JOIN_COLUMN_COLLISION",
116
+ "right.columns",
117
+ f"non-key columns collide with the left table: {overlaps}",
118
+ )
119
+ right_extra_indexes = tuple(
120
+ index for index, column in enumerate(right.columns) if column != join_key
121
+ )
122
+ left_keys = _unique_non_null_keys(left.rows, left_key_index, "left")
123
+ right_keys = _unique_non_null_keys(right.rows, right_key_index, "right")
124
+ right_by_key = {
125
+ row[right_key_index]: tuple(row[index] for index in right_extra_indexes)
126
+ for row in right.rows
127
+ }
128
+ unmatched = tuple(key for key in left_keys if key not in right_keys)
129
+ if unmatched:
130
+ raise SourceContractError(
131
+ "JOIN_UNMATCHED_LEFT",
132
+ "right.rows",
133
+ f"right table is missing {len(unmatched)} left keys",
134
+ )
135
+ output_rows = tuple((*row, *right_by_key[row[left_key_index]]) for row in left.rows)
136
+ if len(output_rows) != len(left.rows):
137
+ raise SourceContractError(
138
+ "JOIN_ROW_MULTIPLICATION",
139
+ "output_rows",
140
+ "one-to-one join changed the left row count",
141
+ )
142
+ return OneToOneJoinEvidence(
143
+ left_input_sha256=left.input_sha256,
144
+ right_input_sha256=right.input_sha256,
145
+ join_key=join_key,
146
+ left_rows=len(left.rows),
147
+ right_rows=len(right.rows),
148
+ output_rows=len(output_rows),
149
+ unmatched_left_rows=0,
150
+ row_multiplier=1.0,
151
+ output_columns=(*left.columns, *right_extra_columns),
152
+ output_rows_value=output_rows,
153
+ )
154
+
155
+
156
+ def _require_acquired_table_binding(
157
+ output: AcquisitionOutput,
158
+ table: ParsedTable,
159
+ side: str,
160
+ ) -> None:
161
+ if (
162
+ output.parsed_schema_digest != table.schema_digest
163
+ or output.receipt.snapshot.content_sha256 != table.input_sha256
164
+ or output.receipt.observation_digest != output.observation.digest
165
+ ):
166
+ raise SourceContractError(
167
+ "ACQUIRED_TABLE_BINDING",
168
+ f"{side}.acquisition",
169
+ "parsed table is not bound to the exact receipt and observation",
170
+ )
171
+ content = output.snapshot_content
172
+ if content is None:
173
+ raise SourceContractError(
174
+ "ACQUIRED_SNAPSHOT",
175
+ f"{side}.snapshot_content",
176
+ "registry-authenticated snapshot bytes are unavailable",
177
+ )
178
+ if (
179
+ sha256_bytes(content) != output.receipt.snapshot.content_sha256
180
+ or len(content) != output.receipt.snapshot.size_bytes
181
+ ):
182
+ raise SourceContractError(
183
+ "ACQUIRED_SNAPSHOT",
184
+ f"{side}.snapshot_content",
185
+ "authenticated snapshot bytes do not match the exact acquisition receipt",
186
+ )
187
+
188
+
189
+ def _unique_non_null_keys(
190
+ rows: tuple[tuple[Any, ...], ...],
191
+ key_index: int,
192
+ side: str,
193
+ ) -> frozenset[Any]:
194
+ keys: list[Any] = []
195
+ for row in rows:
196
+ key = row[key_index]
197
+ if key is None or key == "":
198
+ raise SourceContractError(
199
+ "JOIN_NULL_KEY",
200
+ f"{side}.rows",
201
+ "join keys must be non-null and non-empty",
202
+ )
203
+ keys.append(key)
204
+ try:
205
+ unique_keys = frozenset(keys)
206
+ except TypeError:
207
+ raise SourceContractError(
208
+ "JOIN_KEY_TYPE",
209
+ f"{side}.rows",
210
+ "join keys must be scalar hashable values",
211
+ ) from None
212
+ if len(unique_keys) != len(keys):
213
+ raise SourceContractError(
214
+ "JOIN_CARDINALITY",
215
+ f"{side}.rows",
216
+ "join keys must be unique for a one-to-one integration",
217
+ )
218
+ return unique_keys
@@ -0,0 +1,418 @@
1
+ """Compose a range acquisition with a pinned Reader and immutable revision evidence.
2
+
3
+ This module owns the public seam that neither the range crawler nor a Reader can own alone.
4
+ The crawler returns exact selected messages and source-relative accounting; the Reader turns one
5
+ message into canonical table bytes. The seam binds those two outcomes before returning anything
6
+ a recipe may seal.
7
+
8
+ It is intentionally source-general. HRRR supplies the first task, but no point, variable, host,
9
+ cycle cadence, or expected value is embedded here.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from dataclasses import dataclass
16
+ from typing import Any, Protocol
17
+
18
+ from mostlyright.data_harness.acquisition.http import RetrievalLimits
19
+ from mostlyright.data_harness.acquisition.parsing import ParseLimits
20
+ from mostlyright.data_harness.acquisition.sandbox import (
21
+ CrawlerSandbox,
22
+ FetchedMember,
23
+ SandboxResult,
24
+ )
25
+ from mostlyright.data_harness.acquisition.url_policy import AcquisitionSecurityError
26
+ from mostlyright.data_harness.canonical import canonical_sha256, sha256_bytes
27
+ from mostlyright.data_harness.readers.contracts import ReaderBudgets, ReaderPin
28
+
29
+ MAX_SOURCE_OBJECT_BYTES = 1 << 44
30
+ _DIGEST = re.compile(r"^[0-9a-f]{64}$")
31
+
32
+
33
+ class _RangeReaderSandbox(Protocol):
34
+ def fetch_ranges(self, **kwargs: Any) -> SandboxResult: ...
35
+
36
+ def decode_and_parse(self, **kwargs: Any) -> SandboxResult: ...
37
+
38
+
39
+ def _digest(value: str, subject: str) -> str:
40
+ if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
41
+ raise AcquisitionSecurityError("RANGE_READER_LOCK", f"{subject} is not a SHA-256")
42
+ return value
43
+
44
+
45
+ def _positive(value: int, subject: str, *, maximum: int = MAX_SOURCE_OBJECT_BYTES) -> int:
46
+ if type(value) is not int or not 1 <= value <= maximum:
47
+ raise AcquisitionSecurityError(
48
+ "RANGE_READER_LOCK",
49
+ f"{subject} must be a positive bounded integer",
50
+ )
51
+ return value
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class LockedSlice:
56
+ """One selected message in the operator-frozen source revision."""
57
+
58
+ descriptor: str
59
+ index: int
60
+ first_byte: int
61
+ last_byte: int
62
+ sha256: str
63
+
64
+ def __post_init__(self) -> None:
65
+ if not isinstance(self.descriptor, str) or not self.descriptor:
66
+ raise AcquisitionSecurityError("RANGE_READER_LOCK", "slice descriptor is empty")
67
+ _positive(self.index, "slice index", maximum=1_000_000)
68
+ if (
69
+ type(self.first_byte) is not int
70
+ or type(self.last_byte) is not int
71
+ or self.first_byte < 0
72
+ or self.last_byte < self.first_byte
73
+ or self.last_byte >= MAX_SOURCE_OBJECT_BYTES
74
+ ):
75
+ raise AcquisitionSecurityError("RANGE_READER_LOCK", "slice byte range is invalid")
76
+ _digest(self.sha256, "slice digest")
77
+
78
+ @property
79
+ def size_bytes(self) -> int:
80
+ return self.last_byte - self.first_byte + 1
81
+
82
+ def to_dict(self) -> dict[str, Any]:
83
+ return {
84
+ "descriptor": self.descriptor,
85
+ "index": self.index,
86
+ "first_byte": self.first_byte,
87
+ "last_byte": self.last_byte,
88
+ "size_bytes": self.size_bytes,
89
+ "sha256": self.sha256,
90
+ }
91
+
92
+
93
+ @dataclass(frozen=True)
94
+ class RangeRevisionLock:
95
+ """The source revision the coordinator selected before contestant execution."""
96
+
97
+ cycle: str
98
+ sidecar_url: str
99
+ sidecar_final_url: str
100
+ object_url: str
101
+ sidecar_sha256: str
102
+ sidecar_size_bytes: int
103
+ sidecar_etag: str | None
104
+ sidecar_last_modified: str | None
105
+ object_etag: str | None
106
+ object_last_modified: str | None
107
+ full_object_size_bytes: int
108
+ slices: tuple[LockedSlice, ...]
109
+
110
+ def __post_init__(self) -> None:
111
+ if not isinstance(self.cycle, str) or not self.cycle:
112
+ raise AcquisitionSecurityError("RANGE_READER_LOCK", "cycle is empty")
113
+ for subject, url in (
114
+ ("sidecar URL", self.sidecar_url),
115
+ ("sidecar final URL", self.sidecar_final_url),
116
+ ("object URL", self.object_url),
117
+ ):
118
+ if not isinstance(url, str) or not url.startswith("https://") or len(url) > 2_048:
119
+ raise AcquisitionSecurityError("RANGE_READER_LOCK", f"{subject} is invalid")
120
+ _digest(self.sidecar_sha256, "sidecar digest")
121
+ _positive(self.sidecar_size_bytes, "sidecar size", maximum=1 << 30)
122
+ _positive(self.full_object_size_bytes, "full object size")
123
+ for subject, pair in (
124
+ ("sidecar", (self.sidecar_etag, self.sidecar_last_modified)),
125
+ ("object", (self.object_etag, self.object_last_modified)),
126
+ ):
127
+ if pair == (None, None):
128
+ raise AcquisitionSecurityError(
129
+ "RANGE_READER_LOCK", f"{subject} revision has no HTTP validator"
130
+ )
131
+ for value in pair:
132
+ if value is not None and (
133
+ not isinstance(value, str) or not 1 <= len(value) <= 1_024
134
+ ):
135
+ raise AcquisitionSecurityError(
136
+ "RANGE_READER_LOCK", f"{subject} validator is invalid"
137
+ )
138
+ if not isinstance(self.slices, tuple) or len(self.slices) != 1:
139
+ raise AcquisitionSecurityError(
140
+ "RANGE_READER_LOCK",
141
+ "this contract version requires exactly one selected message",
142
+ )
143
+ if self.slices[0].last_byte >= self.full_object_size_bytes:
144
+ raise AcquisitionSecurityError(
145
+ "RANGE_READER_LOCK",
146
+ "selected message runs past the locked object",
147
+ )
148
+
149
+ @property
150
+ def digest(self) -> str:
151
+ return canonical_sha256(self.to_dict())
152
+
153
+ @property
154
+ def source_revision_digest(self) -> str:
155
+ return canonical_sha256(
156
+ {
157
+ "cycle": self.cycle,
158
+ "sidecar_final_url": self.sidecar_final_url,
159
+ "sidecar_sha256": self.sidecar_sha256,
160
+ "sidecar_size_bytes": self.sidecar_size_bytes,
161
+ "sidecar_etag": self.sidecar_etag,
162
+ "sidecar_last_modified": self.sidecar_last_modified,
163
+ "object_url": self.object_url,
164
+ "object_etag": self.object_etag,
165
+ "object_last_modified": self.object_last_modified,
166
+ "full_object_size_bytes": self.full_object_size_bytes,
167
+ "slices": [item.to_dict() for item in self.slices],
168
+ }
169
+ )
170
+
171
+ def to_dict(self) -> dict[str, Any]:
172
+ return {
173
+ "cycle": self.cycle,
174
+ "sidecar_url": self.sidecar_url,
175
+ "sidecar_final_url": self.sidecar_final_url,
176
+ "object_url": self.object_url,
177
+ "sidecar_sha256": self.sidecar_sha256,
178
+ "sidecar_size_bytes": self.sidecar_size_bytes,
179
+ "sidecar_etag": self.sidecar_etag,
180
+ "sidecar_last_modified": self.sidecar_last_modified,
181
+ "object_etag": self.object_etag,
182
+ "object_last_modified": self.object_last_modified,
183
+ "full_object_size_bytes": self.full_object_size_bytes,
184
+ "slices": [item.to_dict() for item in self.slices],
185
+ }
186
+
187
+ @classmethod
188
+ def from_dict(cls, value: Any) -> RangeRevisionLock:
189
+ if not isinstance(value, dict) or set(value) != {
190
+ "cycle",
191
+ "sidecar_url",
192
+ "sidecar_final_url",
193
+ "object_url",
194
+ "sidecar_sha256",
195
+ "sidecar_size_bytes",
196
+ "sidecar_etag",
197
+ "sidecar_last_modified",
198
+ "object_etag",
199
+ "object_last_modified",
200
+ "full_object_size_bytes",
201
+ "slices",
202
+ }:
203
+ raise AcquisitionSecurityError("RANGE_READER_LOCK", "revision lock shape is invalid")
204
+ raw_slices = value["slices"]
205
+ if not isinstance(raw_slices, list):
206
+ raise AcquisitionSecurityError("RANGE_READER_LOCK", "lock slices are not an array")
207
+ slices: list[LockedSlice] = []
208
+ for raw in raw_slices:
209
+ if not isinstance(raw, dict) or set(raw) != {
210
+ "descriptor",
211
+ "index",
212
+ "first_byte",
213
+ "last_byte",
214
+ "size_bytes",
215
+ "sha256",
216
+ }:
217
+ raise AcquisitionSecurityError("RANGE_READER_LOCK", "locked slice shape is invalid")
218
+ stated_size = raw["size_bytes"]
219
+ item = LockedSlice(
220
+ descriptor=raw["descriptor"],
221
+ index=raw["index"],
222
+ first_byte=raw["first_byte"],
223
+ last_byte=raw["last_byte"],
224
+ sha256=raw["sha256"],
225
+ )
226
+ if stated_size != item.size_bytes:
227
+ raise AcquisitionSecurityError("RANGE_READER_LOCK", "locked slice size is invalid")
228
+ slices.append(item)
229
+ kwargs = dict(value)
230
+ kwargs.pop("slices")
231
+ return cls(**kwargs, slices=tuple(slices))
232
+
233
+
234
+ @dataclass(frozen=True)
235
+ class RangeReaderOutput:
236
+ """Canonical Reader bytes plus the exact recipe-consumable evidence document."""
237
+
238
+ content: bytes
239
+ media_type: str
240
+ parsed_schema_digest: str
241
+ decode_flags: tuple[str, ...]
242
+ fetched_members: tuple[FetchedMember, ...]
243
+ transport_evidence_digest: str
244
+ efficiency_numerator: int
245
+
246
+
247
+ def acquire_range_reader(
248
+ sandbox: _RangeReaderSandbox,
249
+ *,
250
+ request_id: str,
251
+ lock: RangeRevisionLock,
252
+ allowed_hostnames: tuple[str, ...],
253
+ retrieval_limits: RetrievalLimits,
254
+ reader_pin: ReaderPin,
255
+ reader_budgets: ReaderBudgets,
256
+ parse_limits: ParseLimits,
257
+ expected_fetch_policy_digest: str,
258
+ expected_decode_policy_digest: str,
259
+ ) -> RangeReaderOutput:
260
+ """Fetch, revision-check, decode and bind one selected message without a glue-script gap."""
261
+
262
+ _digest(expected_fetch_policy_digest, "fetch policy digest")
263
+ _digest(expected_decode_policy_digest, "decode policy digest")
264
+ fetched = sandbox.fetch_ranges(
265
+ request_id=request_id,
266
+ sidecar_url=lock.sidecar_url,
267
+ object_url=lock.object_url,
268
+ selectors=tuple(item.descriptor for item in lock.slices),
269
+ allowed_hostnames=allowed_hostnames,
270
+ retrieval_limits=retrieval_limits,
271
+ expected_reference_time=lock.cycle,
272
+ )
273
+ if fetched.policy_digest != expected_fetch_policy_digest:
274
+ raise AcquisitionSecurityError(
275
+ "RANGE_READER_POLICY",
276
+ "range result does not bind the coordinator-pinned fetch policy",
277
+ )
278
+ if (
279
+ fetched.ranges is None
280
+ or len(fetched.ranges) != len(lock.slices)
281
+ or fetched.sidecar_content_sha256 != lock.sidecar_sha256
282
+ or fetched.sidecar_size_bytes != lock.sidecar_size_bytes
283
+ or fetched.full_object_size_bytes != lock.full_object_size_bytes
284
+ or fetched.fetched_members is None
285
+ or fetched.transport_evidence_digest is None
286
+ or fetched.final_url is None
287
+ ):
288
+ raise AcquisitionSecurityError(
289
+ "RANGE_READER_REVISION",
290
+ "range result does not match the locked sidecar and object revision",
291
+ )
292
+ for delivered, expected in zip(fetched.ranges, lock.slices, strict=True):
293
+ if (
294
+ delivered.descriptor != expected.descriptor
295
+ or delivered.index != expected.index
296
+ or delivered.first_byte != expected.first_byte
297
+ or delivered.last_byte != expected.last_byte
298
+ or delivered.sha256 != expected.sha256
299
+ or len(delivered.content) != expected.size_bytes
300
+ ):
301
+ raise AcquisitionSecurityError(
302
+ "RANGE_READER_REVISION",
303
+ "delivered message differs from the locked selection",
304
+ )
305
+
306
+ members = fetched.fetched_members
307
+ roles = tuple(member.role for member in members)
308
+ terminal = {
309
+ role: members[max(index for index, value in enumerate(roles) if value == role)]
310
+ for role in ("cycle_probe", "sidecar_initial", "selected_slice", "sidecar_final")
311
+ }
312
+ sidecar_initial = terminal["sidecar_initial"]
313
+ sidecar_final = terminal["sidecar_final"]
314
+ object_probe = terminal["cycle_probe"]
315
+ object_slice = terminal["selected_slice"]
316
+ if (
317
+ fetched.final_url != lock.sidecar_final_url
318
+ or sidecar_initial.url != lock.sidecar_final_url
319
+ or sidecar_final.url != lock.sidecar_final_url
320
+ or sidecar_initial.response_body_sha256 != lock.sidecar_sha256
321
+ or sidecar_final.response_body_sha256 != lock.sidecar_sha256
322
+ or sidecar_initial.response_body_size_bytes != lock.sidecar_size_bytes
323
+ or sidecar_final.response_body_size_bytes != lock.sidecar_size_bytes
324
+ or (sidecar_initial.etag, sidecar_initial.last_modified)
325
+ != (lock.sidecar_etag, lock.sidecar_last_modified)
326
+ or (sidecar_final.etag, sidecar_final.last_modified)
327
+ != (lock.sidecar_etag, lock.sidecar_last_modified)
328
+ or object_probe.url != lock.object_url
329
+ or object_slice.url != lock.object_url
330
+ or (object_probe.etag, object_probe.last_modified)
331
+ != (lock.object_etag, lock.object_last_modified)
332
+ or (object_slice.etag, object_slice.last_modified)
333
+ != (lock.object_etag, lock.object_last_modified)
334
+ ):
335
+ raise AcquisitionSecurityError(
336
+ "RANGE_READER_REVISION",
337
+ "response validators or final member identities differ from the locked revision",
338
+ )
339
+
340
+ selected = fetched.ranges[0]
341
+ decoded = sandbox.decode_and_parse(
342
+ request_id=f"{request_id}.decode",
343
+ content=selected.content,
344
+ reader_pin={
345
+ "family_id": reader_pin.family_id,
346
+ "family_version": reader_pin.family_version,
347
+ "decode_options": dict(reader_pin.decode_options),
348
+ },
349
+ output_format="csv",
350
+ limits=parse_limits,
351
+ reader_budgets={
352
+ name: getattr(reader_budgets, name) for name in reader_budgets.__dataclass_fields__
353
+ },
354
+ )
355
+ if decoded.policy_digest != expected_decode_policy_digest:
356
+ raise AcquisitionSecurityError(
357
+ "RANGE_READER_POLICY",
358
+ "decode result does not bind the coordinator-pinned decode policy",
359
+ )
360
+ if (
361
+ decoded.content is None
362
+ or decoded.media_type is None
363
+ or decoded.parsed is None
364
+ or decoded.parsed.input_sha256 != sha256_bytes(decoded.content)
365
+ or decoded.decode_family_id != reader_pin.family_id
366
+ or decoded.decode_family_version != reader_pin.family_version
367
+ or decoded.decode_options_digest != reader_pin.options_digest
368
+ or decoded.decode_flags is None
369
+ ):
370
+ raise AcquisitionSecurityError(
371
+ "RANGE_READER_DECODE",
372
+ "decode result is incomplete or does not match the pinned Reader",
373
+ )
374
+
375
+ # Receipt v2 historically measures retained response bodies only. The HTTP and sandbox
376
+ # runtime enforce the stronger cumulative budget (including failed-peer partial bodies), but
377
+ # changing this sealed formula would invalidate already published v2 receipts.
378
+ numerator = sum(member.response_body_size_bytes for member in members)
379
+ if numerator * 100 > lock.full_object_size_bytes:
380
+ raise AcquisitionSecurityError(
381
+ "RANGE_READER_EFFICIENCY",
382
+ "source response bodies exceed one percent of the locked object",
383
+ )
384
+ return RangeReaderOutput(
385
+ content=decoded.content,
386
+ media_type=decoded.media_type,
387
+ parsed_schema_digest=decoded.parsed.schema_digest,
388
+ decode_flags=decoded.decode_flags,
389
+ fetched_members=members,
390
+ transport_evidence_digest=fetched.transport_evidence_digest,
391
+ efficiency_numerator=numerator,
392
+ )
393
+
394
+
395
+ def default_policy_digests() -> tuple[str, str]:
396
+ """Expose the exact two sandbox authorities an operator must pin for this composition."""
397
+
398
+ from mostlyright.data_harness.acquisition.sandbox import sandbox_policy_digest
399
+
400
+ return sandbox_policy_digest("fetch_ranges"), sandbox_policy_digest("decode_and_parse")
401
+
402
+
403
+ def range_reader_policy_digest() -> str:
404
+ """Coordinator pin for the exact two sandbox policies used by the composed adapter."""
405
+
406
+ fetch, decode = default_policy_digests()
407
+ return canonical_sha256({"fetch_ranges": fetch, "decode_and_parse": decode})
408
+
409
+
410
+ def require_crawler_sandbox(value: Any) -> CrawlerSandbox:
411
+ """A narrow runtime guard for callers constructing the production path."""
412
+
413
+ if not isinstance(value, CrawlerSandbox):
414
+ raise AcquisitionSecurityError(
415
+ "RANGE_READER_SANDBOX",
416
+ "range Reader acquisition needs the shipped clean-room runner",
417
+ )
418
+ return value