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,477 @@
1
+ """Admission rules that keep the shared catalog public by construction.
2
+
3
+ The catalog is shared across tenants. Harvested bytes therefore pass the repository's bounded
4
+ public-content classifier, and every typed evidence locator is parsed and checked against the
5
+ coordinator's public-host policy before an entry can be sealed.
6
+
7
+ Prose has a stricter, structural contract: it carries no locators. If any conservative form of a
8
+ prose string contains a URL-scheme shape, the entry is refused and the locator must move to a typed
9
+ citation field. Over-detection costs one refused entry; a parser/scanner disagreement cannot admit
10
+ tenant-private text because no prose locator is admitted at all.
11
+
12
+ The walk remains derived from ``CatalogEntry.to_dict()`` -- the exact shape written to
13
+ ``catalog.json`` -- so every serialized prose field is covered without enumerating field names.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import ipaddress
19
+ import re
20
+ import unicodedata
21
+ from collections.abc import Iterator
22
+ from typing import Any
23
+ from urllib.parse import SplitResult, unquote, urlsplit
24
+
25
+ from mostlyright.data_harness.acquisition.url_policy import (
26
+ AcquisitionSecurityError,
27
+ EgressPolicy,
28
+ canonical_public_hostname,
29
+ )
30
+ from mostlyright.data_harness.sources.catalog.contracts import CatalogEntry
31
+ from mostlyright.data_harness.sources.contracts import SourceContractError
32
+ from mostlyright.data_harness.sources.governance import (
33
+ ClassificationResult,
34
+ classify_and_require_admissible,
35
+ )
36
+
37
+ # Four MiB. Deliberately four times tighter than the classifier's own 16 MiB scan cap: harvested
38
+ # metadata documents are small, and the tight cap is what stops an SDMX-scale structure response
39
+ # (the observed Eurostat one is 37,166,239 bytes) from ever reaching a scan.
40
+ MAX_CATALOG_FACT_BYTES = 4 * 1024 * 1024
41
+
42
+ CATALOG_CLASSIFICATION = "public"
43
+
44
+ # Names that are never a public source, regardless of what DNS would say about them.
45
+ _FORBIDDEN_HOST_SUFFIXES = (".local", ".internal", ".localdomain")
46
+ _FORBIDDEN_HOST_NAMES = frozenset({"localhost"})
47
+
48
+ _CITATION_PORT = 443
49
+
50
+ # Special schemes can introduce a URL directly at their colon; other schemes can introduce an
51
+ # authority when followed by a slash or backslash. These deliberately broad patterns answer only
52
+ # whether prose can carry a locator. They never try to find its end or decide which host it names.
53
+ _SPECIAL_SCHEMES = ("file", "ftp", "http", "https", "ws", "wss")
54
+ _SPECIAL_SCHEME_COLON = re.compile(
55
+ r"(?:" + "|".join(_SPECIAL_SCHEMES) + r"):",
56
+ re.IGNORECASE,
57
+ )
58
+ _ANY_SCHEME_AUTHORITY = re.compile(r"[A-Za-z][A-Za-z0-9+.\-]*:[/\\]")
59
+ _SCHEME_RELATIVE_AUTHORITY = re.compile(r"[/\\]{2}")
60
+
61
+ # Python 3.13 ships Unicode 15.1 data while the WHATWG consumer oracle already uses Unicode 16.
62
+ # Unicode 16 assigned the contiguous OUTLINED LATIN CAPITAL LETTER A..Z and OUTLINED DIGIT
63
+ # ZERO..NINE block at U+1CCD6..U+1CCF9, with compatibility decompositions to ASCII. Keep that
64
+ # complete delta explicit instead of branching on ``unicodedata.unidata_version``. Translation is
65
+ # idempotent when the runtime NFKC implementation already normalizes the block.
66
+ _OUTLINED_ALPHANUMERICS = "".join(chr(codepoint) for codepoint in range(0x1CCD6, 0x1CCFA))
67
+ _OUTLINED_ALPHANUMERIC_ASCII = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
68
+ _UNICODE_16_NFKC_DELTA = str.maketrans(
69
+ _OUTLINED_ALPHANUMERICS,
70
+ _OUTLINED_ALPHANUMERIC_ASCII,
71
+ )
72
+
73
+
74
+ def _consumer_nfkc(text: str) -> str:
75
+ """NFKC plus the pinned Unicode 16 delta used by current URL consumers."""
76
+
77
+ return unicodedata.normalize("NFKC", text).translate(_UNICODE_16_NFKC_DELTA)
78
+
79
+
80
+ def _characters_a_url_parser_deletes() -> str:
81
+ """Return C0 characters that the active URL parser removes from a hostname.
82
+
83
+ Deriving the set from the parser avoids a duplicate parser table.
84
+ """
85
+
86
+ deleted: list[str] = []
87
+ for code in range(0x21):
88
+ character = chr(code)
89
+ try:
90
+ reads = urlsplit(f"https://a{character}b/").hostname
91
+ except ValueError:
92
+ continue
93
+ if reads == "ab":
94
+ deleted.append(character)
95
+ return "".join(deleted)
96
+
97
+
98
+ # Deletion can assemble a scheme token that was not contiguous in the written form, so the prose
99
+ # predicate checks the parser-deleted form as well.
100
+ _PARSER_DELETES = _characters_a_url_parser_deletes()
101
+
102
+
103
+ def _the_deletions_a_url_parser_makes(text: str) -> tuple[str, frozenset[int]]:
104
+ """``text`` with the deleted characters gone, and the positions they were deleted from.
105
+
106
+ One pass that reports its own seams, because the rest of this module has to be able to tell a
107
+ character that was *written* next to another one from a character a deletion pulled up against
108
+ it. A seam at index ``i`` of the returned text means at least one deleted character stood
109
+ between ``text[i - 1]`` and ``text[i]`` as the author wrote them.
110
+
111
+ A deleted character can appear before a scheme token or inside the scheme name, as in
112
+ ``ht<LF>tps:``. The written form and the deletion-normalized form cannot independently preserve
113
+ that boundary; the seam does so for every parser-deleted character and position.
114
+ """
115
+
116
+ kept: list[str] = []
117
+ seams: set[int] = set()
118
+ for character in text:
119
+ if character in _PARSER_DELETES:
120
+ seams.add(len(kept))
121
+ else:
122
+ kept.append(character)
123
+ return "".join(kept), frozenset(seams)
124
+
125
+
126
+ class CatalogPrivacyError(SourceContractError):
127
+ """A refusal taken to keep tenant-private or non-public content out of the catalog.
128
+
129
+ It subclasses ``SourceContractError`` so existing fail-closed handling still catches it, and
130
+ is its own type so a privacy refusal is never mistaken for an ordinary validation failure in
131
+ a log or a test.
132
+ """
133
+
134
+
135
+ def admit_public_fact_bytes(
136
+ content: bytes,
137
+ *,
138
+ lawful_basis_declared: bool = True,
139
+ declared_classification: str = CATALOG_CLASSIFICATION,
140
+ ) -> ClassificationResult:
141
+ """Admit bounded public bytes as catalog facts, or refuse them with a typed error.
142
+
143
+ ``declared_classification`` exists only so a caller that passes the wrong thing gets a loud
144
+ refusal instead of quietly widening the catalog. There is no value of it that admits
145
+ non-public content.
146
+ """
147
+
148
+ if not isinstance(content, bytes):
149
+ raise SourceContractError(
150
+ "TYPE",
151
+ "catalog_fact.content",
152
+ "catalog fact content must be bytes",
153
+ )
154
+ if len(content) > MAX_CATALOG_FACT_BYTES:
155
+ raise SourceContractError(
156
+ "CATALOG_FACT_LIMIT",
157
+ "catalog_fact.content",
158
+ f"catalog fact input exceeds {MAX_CATALOG_FACT_BYTES} bytes",
159
+ )
160
+ if declared_classification != CATALOG_CLASSIFICATION:
161
+ raise CatalogPrivacyError(
162
+ "CATALOG_NON_PUBLIC_CLASSIFICATION",
163
+ "catalog_fact.declared_classification",
164
+ "the shared catalog admits public content and nothing else",
165
+ )
166
+ return classify_and_require_admissible(
167
+ content,
168
+ declared_classification=CATALOG_CLASSIFICATION,
169
+ lawful_basis_declared=lawful_basis_declared,
170
+ )
171
+
172
+
173
+ def entry_locators(entry: CatalogEntry) -> tuple[tuple[str, str], ...]:
174
+ """Every typed evidence locator on this entry that has to name a public host.
175
+
176
+ Derived from the entry rather than written out, so this is what the check enumerates *and*
177
+ what a test can assert the check covers. A hand-written list is checkable only against another
178
+ hand-written list, which proves the two lists agree and nothing about the object.
179
+
180
+ An ``EvidenceReference.uri`` is a locator by type: every one of them, wherever it sits on the
181
+ entry, is walked here. ``rights.human_decision_reference`` is free text, not a typed locator;
182
+ opaque ticket IDs remain prose and a URL-shaped value is refused by the prose contract.
183
+ """
184
+
185
+ if not isinstance(entry, CatalogEntry):
186
+ raise SourceContractError("TYPE", "entry", "must be a CatalogEntry")
187
+
188
+ found: list[tuple[str, str]] = []
189
+ for path, references in (
190
+ ("catalog_entry.evidence", entry.evidence),
191
+ ("catalog_entry.rights.evidence", entry.rights.evidence),
192
+ ("catalog_entry.observation.evidence", entry.observation.evidence),
193
+ ):
194
+ found.extend(
195
+ (f"{path}[{index}].uri", reference.uri) for index, reference in enumerate(references)
196
+ )
197
+ return tuple(found)
198
+
199
+
200
+ class _SchemeFormLimit(ValueError):
201
+ """The conservative transform closure exceeded its fail-closed work budget."""
202
+
203
+
204
+ _MAX_SCHEME_FORMS = 64
205
+ _MAX_SCHEME_FORM_CHARACTERS = 256 * 1024
206
+ _FORM_LIMIT_TOKEN = "an encoded locator shape"
207
+ _UNASSIGNED_CODEPOINT_TOKEN = "a runtime-unassigned Unicode code point"
208
+
209
+
210
+ def _scheme_forms(text: str) -> tuple[str, ...]:
211
+ """The bounded fixed-point closure of forms a URL consumer may expose.
212
+
213
+ Percent-decoding, Unicode compatibility normalisation, and parser deletions do not commute.
214
+ Applying each operation only to the written string misses compositions such as a full-width
215
+ percent sign that becomes an escape only after NFKC. The worklist therefore applies every
216
+ transform to every newly discovered form until no form changes.
217
+
218
+ Catalog strings are already bounded by their contracts. The independent form-count and
219
+ aggregate-character budgets make that bound explicit here too; exceeding either budget is a
220
+ fail-closed prose refusal, never permission to stop looking.
221
+ """
222
+
223
+ if not isinstance(text, str):
224
+ raise SourceContractError("TYPE", "catalog_entry.prose", "must be text")
225
+
226
+ forms: list[str] = [text]
227
+ seen = {text}
228
+ total_characters = len(text)
229
+ position = 0
230
+ while position < len(forms):
231
+ current = forms[position]
232
+ position += 1
233
+ deleted, _ = _the_deletions_a_url_parser_makes(current)
234
+ for transformed in (
235
+ unquote(current),
236
+ _consumer_nfkc(current),
237
+ deleted,
238
+ ):
239
+ if transformed in seen:
240
+ continue
241
+ if (
242
+ len(forms) >= _MAX_SCHEME_FORMS
243
+ or total_characters + len(transformed) > _MAX_SCHEME_FORM_CHARACTERS
244
+ ):
245
+ raise _SchemeFormLimit
246
+ seen.add(transformed)
247
+ forms.append(transformed)
248
+ total_characters += len(transformed)
249
+ return tuple(forms)
250
+
251
+
252
+ def _scheme_token_a_parser_could_read(
253
+ text: str,
254
+ *,
255
+ include_scheme_relative: bool = False,
256
+ ) -> str | None:
257
+ """Return one conservative refusal token from any transformed form of the text.
258
+
259
+ Unassigned code points have no stable compatibility normalization. Refuse them rather than
260
+ admit a locator under a different Unicode version.
261
+ """
262
+
263
+ try:
264
+ forms = _scheme_forms(text)
265
+ except _SchemeFormLimit:
266
+ return _FORM_LIMIT_TOKEN
267
+
268
+ for form in forms:
269
+ if unassigned := next(
270
+ (character for character in form if unicodedata.category(character) == "Cn"),
271
+ None,
272
+ ):
273
+ return f"{_UNASSIGNED_CODEPOINT_TOKEN} U+{ord(unassigned):04X}"
274
+ patterns = (_SPECIAL_SCHEME_COLON, _ANY_SCHEME_AUTHORITY)
275
+ if include_scheme_relative:
276
+ patterns += (_SCHEME_RELATIVE_AUTHORITY,)
277
+ matches = tuple(
278
+ match for pattern in patterns if (match := pattern.search(form)) is not None
279
+ )
280
+ if matches:
281
+ return min(matches, key=lambda match: match.start()).group(0)
282
+ return None
283
+
284
+
285
+ def _a_parser_could_read_a_scheme_from(text: str) -> bool:
286
+ """Return whether conservative prose admission finds a scheme or unknown normalization."""
287
+
288
+ return _scheme_token_a_parser_could_read(text) is not None
289
+
290
+
291
+ def entry_prose_carrying_a_locator(entry: CatalogEntry) -> tuple[tuple[str, str], ...]:
292
+ """Every path and scheme token whose prose must be refused.
293
+
294
+ The walk is over the serialized entry rather than a field list. Typed locator values are
295
+ excluded as whole values and checked by _require_public_citation; their path, query, and
296
+ fragment components receive this same scheme-shape rule there so a nested locator cannot move
297
+ from prose into a citation to evade it.
298
+ """
299
+
300
+ if not isinstance(entry, CatalogEntry):
301
+ raise SourceContractError("TYPE", "entry", "must be a CatalogEntry")
302
+ locators = set(entry_locators(entry))
303
+ found: list[tuple[str, str]] = []
304
+ for path, text in _strings_in(entry.to_dict(), "catalog_entry"):
305
+ if (path, text) in locators:
306
+ continue
307
+ if (token := _scheme_token_a_parser_could_read(text)) is not None:
308
+ found.append((path, token))
309
+ return tuple(found)
310
+
311
+
312
+ def _strings_in(node: Any, path: str) -> Iterator[tuple[str, str]]:
313
+ """Every string key and value under ``node``, with its canonical path.
314
+
315
+ Keys are visited in sorted order so the reported path order is the order the canonical JSON
316
+ writer would use, and so a refusal names the same field on every run.
317
+ """
318
+
319
+ if isinstance(node, dict):
320
+ for key in sorted(node):
321
+ # Catalog contract keys are closed, but they are serialized bytes too. Scanning
322
+ # them keeps this walk sound for dynamic-key mappings.
323
+ yield f"{path}.<key>", key
324
+ yield from _strings_in(node[key], f"{path}.{key}")
325
+ elif isinstance(node, list):
326
+ for index, item in enumerate(node):
327
+ yield from _strings_in(item, f"{path}[{index}]")
328
+ elif isinstance(node, str):
329
+ yield path, node
330
+
331
+
332
+ def require_public_source_evidence(entry: CatalogEntry, *, egress_policy: EgressPolicy) -> None:
333
+ """Refuse entries with unsafe typed locators or any locator-shaped prose.
334
+
335
+ Typed locators must be HTTPS, host-check clean, and contain no nested locator in a path, query,
336
+ or fragment. Every other serialized string -- including mapping keys -- is prose and is
337
+ refused when the conservative transform closure exposes a scheme shape. There is no prose
338
+ host allowlist: links belong in typed citation fields.
339
+ """
340
+
341
+ if not isinstance(egress_policy, EgressPolicy):
342
+ raise SourceContractError("TYPE", "egress_policy", "must be an EgressPolicy")
343
+ for path, uri in entry_locators(entry):
344
+ _require_public_citation(uri, path, egress_policy=egress_policy)
345
+ if carrying := entry_prose_carrying_a_locator(entry):
346
+ path, token = carrying[0]
347
+ raise CatalogPrivacyError(
348
+ "CATALOG_PROSE_CARRIES_LOCATOR",
349
+ path,
350
+ f"catalog prose carries unsafe locator material {token!r}; put locators in a typed "
351
+ "citation field and remove runtime-unassigned characters",
352
+ )
353
+
354
+
355
+ def _require_public_citation(uri: str, path: str, *, egress_policy: EgressPolicy) -> None:
356
+ try:
357
+ parsed = urlsplit(uri)
358
+ hostname = parsed.hostname
359
+ except ValueError:
360
+ raise CatalogPrivacyError(
361
+ "CATALOG_EVIDENCE_NOT_PUBLIC",
362
+ path,
363
+ "citation is not a parsable URL",
364
+ ) from None
365
+ if parsed.scheme != "https":
366
+ raise CatalogPrivacyError(
367
+ "CATALOG_EVIDENCE_NOT_PUBLIC",
368
+ path,
369
+ "citations must be HTTPS",
370
+ )
371
+ if not hostname:
372
+ raise CatalogPrivacyError(
373
+ "CATALOG_EVIDENCE_NOT_PUBLIC",
374
+ path,
375
+ "citations must name a host",
376
+ )
377
+ _require_no_nested_locator(parsed, path)
378
+ _require_public_host(
379
+ parsed,
380
+ path,
381
+ egress_policy=egress_policy,
382
+ not_public="CATALOG_EVIDENCE_NOT_PUBLIC",
383
+ not_allowlisted="CATALOG_EVIDENCE_NOT_ALLOWLISTED",
384
+ subject="this citation",
385
+ )
386
+
387
+
388
+ def _require_no_nested_locator(parsed: SplitResult, path: str) -> None:
389
+ """Refuse a locator hidden in any non-authority component of a typed locator."""
390
+
391
+ for component_name, component in (
392
+ ("path", parsed.path),
393
+ ("query", parsed.query),
394
+ ("fragment", parsed.fragment),
395
+ ):
396
+ if (
397
+ token := _scheme_token_a_parser_could_read(
398
+ component,
399
+ include_scheme_relative=True,
400
+ )
401
+ ) is not None:
402
+ raise CatalogPrivacyError(
403
+ "CATALOG_EVIDENCE_CARRIES_NESTED_LOCATOR",
404
+ path,
405
+ f"citation {component_name} carries unsafe locator material {token!r}; a typed "
406
+ "citation names exactly one stable locator",
407
+ )
408
+
409
+
410
+ def _require_public_host(
411
+ parsed: SplitResult,
412
+ path: str,
413
+ *,
414
+ egress_policy: EgressPolicy,
415
+ not_public: str,
416
+ not_allowlisted: str,
417
+ subject: str,
418
+ ) -> None:
419
+ """Apply the canonical public-host rule to the complete parsed locator.
420
+
421
+ Passing the complete locator lets ``canonical_public_hostname`` reject credentials and
422
+ authority delimiters before it normalizes the hostname through IDNA. The allowlist is applied
423
+ only to that canonical name, so every typed citation and serialized locator follows the same
424
+ consumer-compatible rule.
425
+
426
+ It costs false refusals, and they are stated on the customer surface rather than discovered:
427
+ a label over 63 characters, an empty label (``https://.example.org``, ``https://a..b``), a
428
+ label carrying an underscore or a leading or trailing hyphen, and a name over 253 characters
429
+ are all refused. None of them is a name the public DNS resolves; the escape in every case is
430
+ to write the source's real machine name.
431
+ """
432
+
433
+ try:
434
+ host = canonical_public_hostname(parsed)
435
+ except AcquisitionSecurityError:
436
+ raise CatalogPrivacyError(
437
+ not_public,
438
+ path,
439
+ f"{subject} does not spell a machine name a URL parser would resolve, so this module "
440
+ "cannot say which host it names. A catalog names public hosts, and a name it cannot "
441
+ "read is not one",
442
+ ) from None
443
+ # An IP literal is never a catalog link. Refusing the whole shape covers loopback, the
444
+ # private ranges and the cloud metadata address in one rule rather than enumerating them.
445
+ try:
446
+ ipaddress.ip_address(host)
447
+ except ValueError:
448
+ pass
449
+ else:
450
+ raise CatalogPrivacyError(
451
+ not_public,
452
+ path,
453
+ f"{subject} names an address; a catalog names public hosts, never addresses",
454
+ )
455
+ if "." not in host or host in _FORBIDDEN_HOST_NAMES or host.endswith(_FORBIDDEN_HOST_SUFFIXES):
456
+ raise CatalogPrivacyError(
457
+ not_public,
458
+ path,
459
+ f"{subject} names a single-label host or an internal namespace",
460
+ )
461
+ if not egress_policy.permits(host, _CITATION_PORT):
462
+ raise CatalogPrivacyError(
463
+ not_allowlisted,
464
+ path,
465
+ f"{subject} names a host the coordinator has not allowlisted",
466
+ )
467
+ # Last, so a link that is wrong about its host as well as carrying userinfo is still refused
468
+ # for its host: which host a link names is the more useful thing for a refusal to say, and
469
+ # `https://public.example@internal.corp/x` is a host problem wearing a userinfo disguise.
470
+ if parsed.username is not None or parsed.password is not None:
471
+ raise CatalogPrivacyError(
472
+ not_public,
473
+ path,
474
+ f"{subject} carries user information before its host. A public source is addressed "
475
+ "without credentials, and whatever is written there -- a token, a service account, an "
476
+ "internal name -- reaches every tenant verbatim in the shared catalog",
477
+ )