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,447 @@
1
+ """The source health check: a bounded liveness and citation probe.
2
+
3
+ The probe answers one question -- is this public source answering, and what exactly did it say --
4
+ and it answers it without downloading the source's payload. It does that by fetching a small
5
+ declared **citation target**: the bulk index, the CKAN ``package_show`` document, the STAC
6
+ collection document. For FAOSTAT that is a 109,120-byte index carrying an update timestamp for
7
+ every dataset, which is how cadence is learned without touching a 33,921,825-byte archive. If the
8
+ citation target itself exceeds the cap, the probe refuses. There is no fallback to an unbounded
9
+ fetch, and there is no ranged request anywhere on this path.
10
+
11
+ **The probe reports facts. It never reaches a verdict.** It does not import the fitness
12
+ adjudicator, the rights rule, or the catalog gate, and it never returns "selected", "rejected" or
13
+ "human_escalation_required". A probe that started issuing verdicts would be a second gate, and two
14
+ gates disagree. A test proves this structurally rather than trusting the sentence you just read --
15
+ and note that those three words appear in this docstring and nowhere else in the module, which is
16
+ exactly why that test strips docstrings before it looks.
17
+
18
+ Two consequences of that reporting discipline are worth stating.
19
+
20
+ *A blocked egress is not a dead source.* An allowlist, DNS, URL-policy or TLS failure propagates
21
+ untouched, because recording it as "unreachable" would write a local misconfiguration into the
22
+ catalog as a fact about the world. Only codes describing how the endpoint *answered* become an
23
+ unreachable probe.
24
+
25
+ *A refusal is still citable.* Every probe emits an ``EvidenceReference``, including the
26
+ unreachable case, where the citation attests to the refusal rather than to a document. A liveness
27
+ claim nobody can check later is not evidence.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import re
33
+ from dataclasses import dataclass
34
+ from datetime import datetime
35
+ from typing import Any
36
+
37
+ from mostlyright.data_harness.acquisition.http import PinnedHttpsRetriever, RetrievalLimits
38
+ from mostlyright.data_harness.acquisition.url_policy import AcquisitionSecurityError
39
+ from mostlyright.data_harness.canonical import canonical_sha256
40
+ from mostlyright.data_harness.sources.contracts import (
41
+ EvidenceReference,
42
+ SourceContractError,
43
+ SourceObservation,
44
+ _CanonicalContract,
45
+ )
46
+
47
+ HEALTH_PROBE_CONTRACT_VERSION = "harness-source-health-probe.v1"
48
+
49
+ MAX_CITATION_BYTES = 1 * 1024 * 1024
50
+
51
+ CITATION_PROBE_LIMITS = RetrievalLimits(
52
+ max_response_bytes=MAX_CITATION_BYTES,
53
+ max_redirects=2,
54
+ max_requests=3,
55
+ allowed_media_types=("application/json",),
56
+ )
57
+
58
+ # Codes that describe how the endpoint answered. These become an unreachable probe. Every other
59
+ # acquisition code describes this side of the connection and propagates untouched.
60
+ ENDPOINT_FAULT_CODES = frozenset(
61
+ {
62
+ "HTTP_STATUS",
63
+ "MEDIA_TYPE",
64
+ "RESPONSE_LIMIT",
65
+ "CONTENT_LENGTH",
66
+ "CONTENT_ENCODING",
67
+ "REDIRECT_LIMIT",
68
+ "REDIRECT_LOCATION",
69
+ }
70
+ )
71
+
72
+ # The parked item, named verbatim so a 401 reads as a scope boundary rather than a prompt.
73
+ AUTHENTICATED_ADAPTER_PARKED = (
74
+ "this endpoint requires authentication and is out of scope; authenticated source adapters are "
75
+ "parked, and their trigger is the first real credentialed need plus a broker trust review"
76
+ )
77
+
78
+ # Ten years, matching the bound SourceObservation enforces on its second-valued fields.
79
+ MAX_DECLARED_DELAY_SECONDS = 10 * 365 * 24 * 60 * 60
80
+
81
+ _STATUS_TAIL = re.compile(r"status (\d{3})$")
82
+ _SHA256 = re.compile(r"^[0-9a-f]{64}$")
83
+ _TIMESTAMP = re.compile(
84
+ r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,9})?Z$"
85
+ )
86
+
87
+
88
+ def citation_probe_limits(*media_types: str) -> RetrievalLimits:
89
+ """The citation-probe caps, narrowed to one or more media types.
90
+
91
+ STAC and SDMX citation targets are not JSON in every case, and they must not have to redefine
92
+ the caps to say so -- a second copy of the caps is a second thing to forget to tighten.
93
+
94
+ Which non-JSON types actually reach the wire is the transport's decision, not this function's.
95
+ A probe is narrowed onto the coordinator's retriever, so the type named here must be one that
96
+ retriever's allowlist carries or one of the transport's own ``METADATA_MEDIA_TYPES`` -- the
97
+ closed metadata channel that exists because an SDMX structure message is not a payload format
98
+ and so can never be in the format table a default retriever is built from.
99
+ """
100
+
101
+ if not media_types:
102
+ raise SourceContractError(
103
+ "EMPTY",
104
+ "media_types",
105
+ "a citation probe must allow at least one media type",
106
+ )
107
+ return RetrievalLimits(
108
+ max_response_bytes=CITATION_PROBE_LIMITS.max_response_bytes,
109
+ max_redirects=CITATION_PROBE_LIMITS.max_redirects,
110
+ max_requests=CITATION_PROBE_LIMITS.max_requests,
111
+ allowed_media_types=tuple(media_types),
112
+ )
113
+
114
+
115
+ @dataclass(frozen=True)
116
+ class SourceHealthProbe(_CanonicalContract):
117
+ """What one bounded probe of one public source observed, and nothing more."""
118
+
119
+ source_id: str
120
+ citation_uri: str
121
+ probed_at: str
122
+ reachable: bool
123
+ response_status: int | None = None
124
+ media_type: str | None = None
125
+ content_sha256: str | None = None
126
+ content_bytes: int | None = None
127
+ refusal_code: str | None = None
128
+ refusal_detail: str | None = None
129
+ transport_evidence_digest: str | None = None
130
+ schema_version: str = HEALTH_PROBE_CONTRACT_VERSION
131
+
132
+ def __post_init__(self) -> None:
133
+ if self.schema_version != HEALTH_PROBE_CONTRACT_VERSION:
134
+ raise SourceContractError(
135
+ "VERSION",
136
+ "probe.schema_version",
137
+ f"must equal {HEALTH_PROBE_CONTRACT_VERSION!r}",
138
+ )
139
+ _identifier(self.source_id, "probe.source_id")
140
+ _https_uri(self.citation_uri, "probe.citation_uri")
141
+ _timestamp(self.probed_at, "probe.probed_at")
142
+ if type(self.reachable) is not bool:
143
+ raise SourceContractError("BOOLEAN", "probe.reachable", "must be a boolean")
144
+ # The two states are mutually exclusive by construction: a reachable probe carries what it
145
+ # saw, an unreachable one carries why it saw nothing. Neither can carry both.
146
+ if self.reachable:
147
+ _integer(self.response_status, "probe.response_status", minimum=200, maximum=299)
148
+ _media_type(self.media_type, "probe.media_type")
149
+ _digest(self.content_sha256, "probe.content_sha256")
150
+ _integer(
151
+ self.content_bytes,
152
+ "probe.content_bytes",
153
+ minimum=0,
154
+ maximum=MAX_CITATION_BYTES,
155
+ )
156
+ _digest(self.transport_evidence_digest, "probe.transport_evidence_digest")
157
+ if self.refusal_code is not None or self.refusal_detail is not None:
158
+ raise SourceContractError(
159
+ "PROBE_STATE",
160
+ "probe.refusal_code",
161
+ "a reachable probe carries no refusal",
162
+ )
163
+ else:
164
+ if self.refusal_code not in ENDPOINT_FAULT_CODES:
165
+ raise SourceContractError(
166
+ "PROBE_REFUSAL",
167
+ "probe.refusal_code",
168
+ f"must be one of {sorted(ENDPOINT_FAULT_CODES)}",
169
+ )
170
+ _text(self.refusal_detail, "probe.refusal_detail", maximum=500)
171
+ for name in ("media_type", "content_sha256", "content_bytes"):
172
+ if getattr(self, name) is not None:
173
+ raise SourceContractError(
174
+ "PROBE_STATE",
175
+ f"probe.{name}",
176
+ "an unreachable probe retains no content facts",
177
+ )
178
+ if self.response_status is not None:
179
+ _integer(
180
+ self.response_status,
181
+ "probe.response_status",
182
+ minimum=100,
183
+ maximum=599,
184
+ )
185
+
186
+ def to_dict(self) -> dict[str, Any]:
187
+ return {
188
+ "schema_version": self.schema_version,
189
+ "source_id": self.source_id,
190
+ "citation_uri": self.citation_uri,
191
+ "probed_at": self.probed_at,
192
+ "reachable": self.reachable,
193
+ "response_status": self.response_status,
194
+ "media_type": self.media_type,
195
+ "content_sha256": self.content_sha256,
196
+ "content_bytes": self.content_bytes,
197
+ "refusal_code": self.refusal_code,
198
+ "refusal_detail": self.refusal_detail,
199
+ "transport_evidence_digest": self.transport_evidence_digest,
200
+ }
201
+
202
+
203
+ def probe_source_health(
204
+ *,
205
+ source_id: str,
206
+ citation_uri: str,
207
+ retriever: PinnedHttpsRetriever,
208
+ probed_at: str,
209
+ limits: RetrievalLimits = CITATION_PROBE_LIMITS,
210
+ ) -> SourceHealthProbe:
211
+ """Fetch one bounded citation target and record exactly what came back.
212
+
213
+ ``limits`` is accepted so a caller can narrow the media types, and it governs the fetch: it is
214
+ threaded into the transport through ``retriever.narrowed`` rather than merely validated, so a
215
+ probe declaring ``application/json`` under a 1 MiB cap really does refuse an HTML interstitial
216
+ served by a retriever whose own allowlist would have accepted it.
217
+
218
+ Two independent ceilings apply, and both are needed. ``narrowed`` refuses anything looser than
219
+ what the *coordinator* configured, so a probe can never escape the transport it was handed.
220
+ That is not enough on its own: a coordinator's retriever is sized for harvesting, so limits
221
+ well inside it are still far outside what "a bounded citation probe" means. So every
222
+ transport-shape cap this function accepts is also checked against ``CITATION_PROBE_LIMITS``
223
+ before anything is fetched. Checking only the byte cap would have left the redirect and
224
+ request caps enforced by nothing but callers using ``citation_probe_limits`` -- a bound that
225
+ holds while everyone takes the sanctioned route is the pattern this package refuses everywhere
226
+ else.
227
+ """
228
+
229
+ for name in ("max_response_bytes", "max_redirects", "max_requests"):
230
+ asked = getattr(limits, name)
231
+ if asked > getattr(CITATION_PROBE_LIMITS, name):
232
+ raise SourceContractError(
233
+ "PROBE_LIMITS",
234
+ f"limits.{name}",
235
+ f"a citation probe may not exceed {getattr(CITATION_PROBE_LIMITS, name)}; "
236
+ f"{asked} was asked for",
237
+ )
238
+ bounded = retriever.narrowed(limits)
239
+ try:
240
+ retrieved = bounded.retrieve(citation_uri)
241
+ except AcquisitionSecurityError as error:
242
+ if error.code not in ENDPOINT_FAULT_CODES:
243
+ # Egress, DNS, URL policy, TLS, concurrency and rate-limit failures are facts about
244
+ # this side of the connection. Recording one as a dead source would put a local
245
+ # misconfiguration into the catalog as a claim about the world.
246
+ raise
247
+ status = _status_from_detail(error.detail)
248
+ return SourceHealthProbe(
249
+ source_id=source_id,
250
+ citation_uri=citation_uri,
251
+ probed_at=probed_at,
252
+ reachable=False,
253
+ response_status=status,
254
+ refusal_code=error.code,
255
+ refusal_detail=_refusal_detail(error, status),
256
+ )
257
+ return SourceHealthProbe(
258
+ source_id=source_id,
259
+ citation_uri=citation_uri,
260
+ probed_at=probed_at,
261
+ reachable=True,
262
+ response_status=retrieved.hops[-1].status,
263
+ media_type=retrieved.media_type,
264
+ content_sha256=retrieved.content_sha256,
265
+ content_bytes=len(retrieved.content),
266
+ transport_evidence_digest=retrieved.transport_evidence_digest,
267
+ )
268
+
269
+
270
+ def probe_citation(probe: SourceHealthProbe) -> EvidenceReference:
271
+ """The citation this probe leaves behind.
272
+
273
+ For a reachable probe the citation attests to a document: its uri, when it was read, and the
274
+ digest of its exact bytes. For an unreachable probe there are no bytes, so the citation
275
+ attests to the *refusal* -- a canonical digest over the refusal code, the uri and the probe
276
+ time. Either way the claim is checkable later, which is the point.
277
+ """
278
+
279
+ if not isinstance(probe, SourceHealthProbe):
280
+ raise SourceContractError("TYPE", "probe", "must be a SourceHealthProbe")
281
+ if probe.reachable:
282
+ assert probe.content_sha256 is not None
283
+ assert probe.media_type is not None
284
+ return EvidenceReference(
285
+ uri=probe.citation_uri,
286
+ observed_at=probe.probed_at,
287
+ content_sha256=probe.content_sha256,
288
+ media_type=probe.media_type,
289
+ )
290
+ return EvidenceReference(
291
+ uri=probe.citation_uri,
292
+ observed_at=probe.probed_at,
293
+ content_sha256=canonical_sha256(
294
+ {
295
+ "refusal_code": probe.refusal_code,
296
+ "citation_uri": probe.citation_uri,
297
+ "probed_at": probe.probed_at,
298
+ }
299
+ ),
300
+ media_type="application/json",
301
+ )
302
+
303
+
304
+ def build_probe_observation(
305
+ *,
306
+ probe: SourceHealthProbe,
307
+ declared_updated_at: str,
308
+ historical_start: str,
309
+ historical_end: str,
310
+ update_frequency_seconds: int,
311
+ event_time_field: str | None = None,
312
+ available_at_field: str | None = None,
313
+ ) -> SourceObservation:
314
+ """Turn one probe plus the publisher's own declared update time into a source observation.
315
+
316
+ This is the object a ``CatalogEntry`` embeds, which is what stops the probe's view of a source
317
+ and the entry's facts about it from drifting apart.
318
+ """
319
+
320
+ if not isinstance(probe, SourceHealthProbe):
321
+ raise SourceContractError("TYPE", "probe", "must be a SourceHealthProbe")
322
+ probed = _timestamp(probe.probed_at, "probe.probed_at")
323
+ declared = _timestamp(declared_updated_at, "declared_updated_at")
324
+ delay = int((probed - declared).total_seconds())
325
+ if delay < 0:
326
+ # Validate ordering only; timestamp plausibility is outside this function.
327
+ delay = 0
328
+ if delay > MAX_DECLARED_DELAY_SECONDS:
329
+ raise SourceContractError(
330
+ "PROBE_STALE_DECLARATION",
331
+ "declared_updated_at",
332
+ f"the declared update is {delay} seconds old, beyond the "
333
+ f"{MAX_DECLARED_DELAY_SECONDS}-second bound; a decade-stale claim is a fact worth "
334
+ "surfacing rather than clamping into range",
335
+ )
336
+ _integer(
337
+ update_frequency_seconds,
338
+ "update_frequency_seconds",
339
+ minimum=0,
340
+ maximum=MAX_DECLARED_DELAY_SECONDS,
341
+ )
342
+ return SourceObservation(
343
+ source_id=probe.source_id,
344
+ observed_at=probe.probed_at,
345
+ event_time_field=event_time_field,
346
+ available_at_field=available_at_field,
347
+ historical_start=historical_start,
348
+ historical_end=historical_end,
349
+ live_status="live" if probe.reachable else "dead",
350
+ publication_delay_seconds=delay,
351
+ update_frequency_seconds=update_frequency_seconds,
352
+ response_status=probe.response_status,
353
+ evidence=(probe_citation(probe),),
354
+ )
355
+
356
+
357
+ def derive_update_frequency_seconds(earlier: str, later: str) -> int:
358
+ """Whole seconds between two declared update timestamps of the same source."""
359
+
360
+ first = _timestamp(earlier, "earlier")
361
+ second = _timestamp(later, "later")
362
+ seconds = int((second - first).total_seconds())
363
+ if seconds <= 0:
364
+ raise SourceContractError(
365
+ "PROBE_INTERVAL",
366
+ "later",
367
+ "the later declared update must follow the earlier one",
368
+ )
369
+ if seconds > MAX_DECLARED_DELAY_SECONDS:
370
+ raise SourceContractError(
371
+ "PROBE_INTERVAL",
372
+ "later",
373
+ f"an update interval of {seconds} seconds exceeds the observation bound",
374
+ )
375
+ return seconds
376
+
377
+
378
+ def _status_from_detail(detail: str) -> int | None:
379
+ match = _STATUS_TAIL.search(detail)
380
+ if match is None:
381
+ return None
382
+ return int(match.group(1))
383
+
384
+
385
+ def _refusal_detail(error: AcquisitionSecurityError, status: int | None) -> str:
386
+ if status in {401, 403}:
387
+ return f"HTTP {status}: {AUTHENTICATED_ADAPTER_PARKED}"
388
+ return error.detail[:500]
389
+
390
+
391
+ def _text(value: Any, path: str, *, maximum: int) -> str:
392
+ if not isinstance(value, str) or not value or len(value) > maximum:
393
+ raise SourceContractError(
394
+ "TEXT",
395
+ path,
396
+ f"must be a non-empty string no longer than {maximum} characters",
397
+ )
398
+ return value
399
+
400
+
401
+ def _identifier(value: Any, path: str) -> str:
402
+ text = _text(value, path, maximum=128)
403
+ if not re.fullmatch(r"[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*", text):
404
+ raise SourceContractError(
405
+ "IDENTIFIER",
406
+ path,
407
+ "must be a lowercase dotted/dashed/underscored identifier",
408
+ )
409
+ return text
410
+
411
+
412
+ def _https_uri(value: Any, path: str) -> str:
413
+ text = _text(value, path, maximum=2_048)
414
+ if not text.startswith("https://") or len(text) <= len("https://"):
415
+ raise SourceContractError("HTTPS_URI", path, "must be an HTTPS URI")
416
+ return text
417
+
418
+
419
+ def _timestamp(value: Any, path: str) -> datetime:
420
+ text = _text(value, path, maximum=32)
421
+ if not _TIMESTAMP.fullmatch(text):
422
+ raise SourceContractError("TIMESTAMP", path, "must be a canonical RFC 3339 UTC timestamp")
423
+ return datetime.fromisoformat(text[:-1] + "+00:00")
424
+
425
+
426
+ def _media_type(value: Any, path: str) -> str:
427
+ text = _text(value, path, maximum=255)
428
+ if not re.fullmatch(r"[a-z0-9][a-z0-9!#$&^_.+-]*/[a-z0-9][a-z0-9!#$&^_.+-]*", text):
429
+ raise SourceContractError("MEDIA_TYPE", path, "must be a lowercase type/subtype")
430
+ return text
431
+
432
+
433
+ def _digest(value: Any, path: str) -> str:
434
+ text = _text(value, path, maximum=64)
435
+ if not _SHA256.fullmatch(text):
436
+ raise SourceContractError("SHA256", path, "must be a lowercase 64-character SHA-256")
437
+ return text
438
+
439
+
440
+ def _integer(value: Any, path: str, *, minimum: int, maximum: int) -> int:
441
+ if type(value) is not int or not minimum <= value <= maximum:
442
+ raise SourceContractError(
443
+ "INTEGER",
444
+ path,
445
+ f"must be an integer in [{minimum}, {maximum}]",
446
+ )
447
+ return value
@@ -0,0 +1,105 @@
1
+ """Fetch-at-start: giving one hosted worker the exact sealed catalogue its deployment pinned.
2
+
3
+ A local install of the catalogue is a thing a person did once and can see in their home folder. A
4
+ worker has neither -- it is a fresh container per attempt, and the only durable statement about
5
+ what it should be running is its own deploy-owned configuration. So the worker does at start what
6
+ the person did by hand: reads the pin, fetches the pinned manifest, installs the generation the
7
+ manifest names, and verifies it with the real store loader before anything can query it.
8
+
9
+ THREE OUTCOMES, ALL EXPLICIT.
10
+
11
+ * No pin configured -> ``None``. This deployment does not host a catalogue. It is the state every
12
+ deployment is in before the channel is rolled out to it, and it must not be an error.
13
+ * Pin configured and satisfied -> a receipt naming the generation now on disk.
14
+ * Pin configured and not satisfiable -> a refusal, and the worker does not start.
15
+
16
+ The third is the one worth being deliberate about. A worker that cannot get the catalogue it was
17
+ told to have could plausibly carry on -- discovery would simply record the catalogue lane as
18
+ unavailable, which is a state the product already models. It does not carry on, because the two
19
+ situations are indistinguishable from the outside and only one of them is intended: "this
20
+ deployment has no catalogue" is a decision, and "this deployment's catalogue fetch failed" is a
21
+ fault, and a fault that renders as a decision is how a fleet quietly stops using its catalogue.
22
+
23
+ CACHING. The generation lands under a writable path in the container -- ``/tmp`` by default, which
24
+ on Cloud Run is an in-memory filesystem, and the install is append-only, so a warm instance that
25
+ already has the pinned generation re-verifies it and downloads nothing. There is no shared mount:
26
+ the hosted worker image mounts no volume for any other read-only asset either, and introducing one
27
+ for the catalogue would make a per-instance cache into shared mutable state.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import os
33
+ from collections.abc import Mapping
34
+ from pathlib import Path
35
+ from typing import Any
36
+
37
+ from mostlyright.data_harness.sources.catalog.channel import pin_from_environment
38
+ from mostlyright.data_harness.sources.catalog.channel_client import (
39
+ ChannelTransport,
40
+ install_from_channel,
41
+ metadata_access_token,
42
+ )
43
+
44
+ __all__ = [
45
+ "CATALOG_ROOT_ENV",
46
+ "DEFAULT_WORKER_CATALOG_ROOT",
47
+ "HOSTED_CATALOG_RECEIPT_SCHEMA",
48
+ "ensure_hosted_catalog",
49
+ "worker_catalog_root",
50
+ ]
51
+
52
+ #: Where the worker keeps the generation it fetched. Overridable so a job image with a different
53
+ #: writable layout, and every test, can put it somewhere else.
54
+ CATALOG_ROOT_ENV = "MOSTLYRIGHT_WORKER_CATALOG_DIR"
55
+ DEFAULT_WORKER_CATALOG_ROOT = "/tmp/mostlyright-catalog"
56
+
57
+ HOSTED_CATALOG_RECEIPT_SCHEMA = "mostlyright-worker-catalog-fetch.v1"
58
+
59
+
60
+ def worker_catalog_root(environment: Mapping[str, str] | None = None) -> Path:
61
+ """The folder this worker installs the pinned generation into."""
62
+
63
+ env = os.environ if environment is None else environment
64
+ return Path(env.get(CATALOG_ROOT_ENV) or DEFAULT_WORKER_CATALOG_ROOT)
65
+
66
+
67
+ def ensure_hosted_catalog(
68
+ environment: Mapping[str, str] | None = None,
69
+ *,
70
+ transport: ChannelTransport | None = None,
71
+ authorization: str | None = None,
72
+ ) -> dict[str, Any] | None:
73
+ """Put the pinned generation on this worker, or answer None when none is pinned.
74
+
75
+ ``authorization`` is asked of the metadata server when the caller does not supply one, which
76
+ is what a real worker wants and what no test wants. Passing an empty string selects an
77
+ unauthenticated fetch explicitly, for a channel that is world-readable.
78
+ """
79
+
80
+ env = os.environ if environment is None else environment
81
+ pin = pin_from_environment(env)
82
+ if pin is None:
83
+ return None
84
+ if authorization is None:
85
+ authorization = metadata_access_token(transport=transport)
86
+ elif authorization == "":
87
+ authorization = None
88
+ root = worker_catalog_root(env)
89
+ root.mkdir(parents=True, exist_ok=True)
90
+ report = install_from_channel(
91
+ root, pin, transport=transport, authorization=authorization or None
92
+ )
93
+ return {
94
+ "schema_version": HOSTED_CATALOG_RECEIPT_SCHEMA,
95
+ "pin_selection_id": pin.selection_id,
96
+ "channel_url": pin.channel_url,
97
+ "manifest_sha256": pin.manifest_sha256,
98
+ "status": report.status,
99
+ "store_kind": report.state.store_kind,
100
+ "store_sha256": report.state.store_sha256,
101
+ "archive_sha256": report.state.archive_sha256,
102
+ "entry_count": report.state.entry_count,
103
+ "catalog_root": str(root),
104
+ "generation_root": os.path.join(str(root), report.state.generation_path),
105
+ }