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,356 @@
1
+ """Capability-free Cloud Run Job bootstrap for one resumable ingestion delivery.
2
+
3
+ This is deliberately a small front door, not another worker protocol. The Job receives exactly
4
+ an opaque dispatch id and the Studio endpoint/audience derived from its image configuration. Its
5
+ ambient workload identity redeems that id once. All durable progress is represented by the
6
+ generation- and digest-pinned manifest reference in :mod:`hosted_ingestion_contract`; a process
7
+ that reaches its checkpoint reserve exits before credential expiry rather than trying to stretch a
8
+ delivery lease.
9
+
10
+ There is no generic source fetcher in this module. The only admitted first-slice continuation is
11
+ the immutable-public deterministic offset contract, and a reviewed connector injects its bounded
12
+ processor through :func:`run_hosted_ingestion_delivery`. This keeps an unwired Cloud Run Job from
13
+ silently treating an arbitrary URL or cursor as executable work.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import os
19
+ import re
20
+ import sys
21
+ from collections.abc import Callable, Sequence
22
+ from dataclasses import dataclass
23
+ from datetime import UTC, datetime
24
+ from http import HTTPStatus
25
+ from typing import Protocol
26
+ from uuid import UUID
27
+
28
+ from mostlyright.data_harness.canonical import CanonicalJSONError, canonical_json_bytes, parse_json
29
+ from mostlyright.data_harness.hosted_bootstrap import (
30
+ BootstrapTransport,
31
+ UrlLibBootstrapTransport,
32
+ _https_url,
33
+ _metadata_identity,
34
+ )
35
+ from mostlyright.data_harness.hosted_ingestion_contract import (
36
+ HOSTED_INGESTION_DISPATCH_SCHEMA,
37
+ MAX_DELIVERY_DEADLINE_SECONDS,
38
+ HostedIngestionDispatchV1,
39
+ parse_delivery_deadline,
40
+ )
41
+ from mostlyright.data_harness.ingestion.contracts import IngestionError
42
+
43
+ INGESTION_DISPATCH_SCHEMA_VERSION = "3.0.0"
44
+ INGESTION_DISPATCH_EXCHANGE_PATH_TEMPLATE = (
45
+ "/internal/v3/ingestion/dispatches/{dispatch_id}:exchange"
46
+ )
47
+ MAX_INGESTION_DISPATCH_RESPONSE_BYTES = 64 * 1024
48
+ CHECKPOINT_RESERVE_SECONDS = 30.0
49
+ MAX_JOB_DEADLINE_SECONDS = 15 * 60
50
+ CONSUMED_INGESTION_DISPATCH_CODES = frozenset(
51
+ {"INGESTION_DISPATCH_ALREADY_CLAIMED", "INGESTION_DISPATCH_CLOSED"}
52
+ )
53
+ _CODE = re.compile(r"[A-Z][A-Z0-9_]{2,127}\Z")
54
+
55
+
56
+ class HostedIngestionJobError(RuntimeError):
57
+ """A coded refusal which is safe to emit from the Job entrypoint."""
58
+
59
+ def __init__(self, code: str, detail: str) -> None:
60
+ super().__init__(f"{code}: {detail}")
61
+ self.code = code
62
+ self.detail = detail
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class IngestionDispatch:
67
+ """Exactly the three opaque/non-secret task arguments permitted by deployment."""
68
+
69
+ dispatch_id: str
70
+ exchange_url: str
71
+ audience: str
72
+
73
+
74
+ @dataclass(frozen=True)
75
+ class IngestionJobConfig:
76
+ """The pinned Studio origin; storage authority stays out of this bootstrap config."""
77
+
78
+ studio_origin: str
79
+
80
+ @classmethod
81
+ def from_environment(cls, environment: dict[str, str] | None = None) -> IngestionJobConfig:
82
+ active = os.environ if environment is None else environment
83
+ origin = active.get("STUDIO_API_ORIGIN")
84
+ if not isinstance(origin, str) or not origin:
85
+ raise HostedIngestionJobError(
86
+ "INGESTION_DISPATCH_CONFIG_INVALID", "STUDIO_API_ORIGIN is required"
87
+ )
88
+ try:
89
+ normalized = _https_url(origin, "STUDIO_API_ORIGIN")
90
+ except (RuntimeError, ValueError) as error:
91
+ raise HostedIngestionJobError(
92
+ "INGESTION_DISPATCH_CONFIG_INVALID", "STUDIO_API_ORIGIN is not HTTPS"
93
+ ) from error
94
+ return cls(studio_origin=normalized.rstrip("/"))
95
+
96
+
97
+ def _uuid4(value: object) -> str:
98
+ if not isinstance(value, str):
99
+ raise HostedIngestionJobError("INGESTION_DISPATCH_INVALID", "dispatch id must be a UUID4")
100
+ try:
101
+ parsed = UUID(value)
102
+ except ValueError as error:
103
+ raise HostedIngestionJobError(
104
+ "INGESTION_DISPATCH_INVALID", "dispatch id must be a UUID4"
105
+ ) from error
106
+ if parsed.version != 4 or str(parsed) != value:
107
+ raise HostedIngestionJobError("INGESTION_DISPATCH_INVALID", "dispatch id must be a UUID4")
108
+ return value
109
+
110
+
111
+ def _https(value: object, label: str) -> str:
112
+ try:
113
+ return _https_url(value, label)
114
+ except (RuntimeError, TypeError, ValueError) as error:
115
+ raise HostedIngestionJobError(
116
+ "INGESTION_DISPATCH_INVALID", f"{label} is not HTTPS"
117
+ ) from error
118
+
119
+
120
+ def parse_ingestion_dispatch(argv: Sequence[str]) -> IngestionDispatch:
121
+ """Parse the only six argv cells deployment may put on an ingestion Job task."""
122
+
123
+ values = tuple(argv)
124
+ flags = (
125
+ "--ingestion-dispatch-id",
126
+ "--ingestion-dispatch-exchange-url",
127
+ "--ingestion-dispatch-audience",
128
+ )
129
+ if len(values) != 6 or values[::2] != flags or any(not value for value in values[1::2]):
130
+ raise HostedIngestionJobError(
131
+ "INGESTION_DISPATCH_ARGUMENTS_INVALID",
132
+ "expected --ingestion-dispatch-id, --ingestion-dispatch-exchange-url and "
133
+ "--ingestion-dispatch-audience in that order",
134
+ )
135
+ dispatch_id, exchange_url, audience = values[1::2]
136
+ return IngestionDispatch(
137
+ dispatch_id=_uuid4(dispatch_id),
138
+ exchange_url=_https(exchange_url, "ingestion dispatch exchange URL"),
139
+ audience=_https(audience, "ingestion dispatch audience"),
140
+ )
141
+
142
+
143
+ def _validate_dispatch(config: IngestionJobConfig, dispatch: IngestionDispatch) -> None:
144
+ expected = config.studio_origin + INGESTION_DISPATCH_EXCHANGE_PATH_TEMPLATE.format(
145
+ dispatch_id=dispatch.dispatch_id
146
+ )
147
+ if dispatch.exchange_url != expected:
148
+ raise HostedIngestionJobError(
149
+ "INGESTION_DISPATCH_URL_UNRECOGNIZED",
150
+ "ingestion dispatch exchange URL is not this deployment's endpoint for this dispatch",
151
+ )
152
+ if dispatch.audience != config.studio_origin:
153
+ raise HostedIngestionJobError(
154
+ "INGESTION_DISPATCH_AUDIENCE_UNRECOGNIZED",
155
+ "ingestion dispatch audience is not this deployment's Studio audience",
156
+ )
157
+
158
+
159
+ def _exchange_error(status: int, raw: bytearray) -> HostedIngestionJobError:
160
+ try:
161
+ document = parse_json(bytes(raw))
162
+ except (CanonicalJSONError, TypeError, ValueError):
163
+ document = None
164
+ if isinstance(document, dict) and isinstance(document.get("code"), str):
165
+ code = document["code"]
166
+ if _CODE.fullmatch(code) is not None:
167
+ return HostedIngestionJobError(
168
+ code, f"ingestion dispatch exchange returned HTTP {status}"
169
+ )
170
+ return HostedIngestionJobError(
171
+ "INGESTION_DISPATCH_EXCHANGE_FAILED",
172
+ f"ingestion dispatch exchange returned HTTP {status}",
173
+ )
174
+
175
+
176
+ def exchange_ingestion_dispatch(
177
+ *,
178
+ config: IngestionJobConfig,
179
+ dispatch: IngestionDispatch,
180
+ transport: BootstrapTransport | None = None,
181
+ ) -> HostedIngestionDispatchV1:
182
+ """Redeem one opaque id through workload identity and parse the closed response document."""
183
+
184
+ _validate_dispatch(config, dispatch)
185
+ active = transport or UrlLibBootstrapTransport()
186
+ identity = _metadata_identity(active, dispatch.audience)
187
+ request = canonical_json_bytes(
188
+ {"schema_version": INGESTION_DISPATCH_SCHEMA_VERSION, "dispatch_id": dispatch.dispatch_id}
189
+ )
190
+ try:
191
+ try:
192
+ token = identity.decode("ascii")
193
+ except UnicodeDecodeError as error: # pragma: no cover - metadata helper validates this
194
+ raise HostedIngestionJobError(
195
+ "INGESTION_DISPATCH_IDENTITY_INVALID", "workload identity was not ASCII"
196
+ ) from error
197
+ status, response = active.request(
198
+ "POST",
199
+ dispatch.exchange_url,
200
+ {"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
201
+ request,
202
+ MAX_INGESTION_DISPATCH_RESPONSE_BYTES,
203
+ )
204
+ finally:
205
+ identity[:] = b"\x00" * len(identity)
206
+ raw = bytearray(response)
207
+ try:
208
+ if status != HTTPStatus.OK:
209
+ raise _exchange_error(status, raw)
210
+ try:
211
+ document = parse_json(bytes(raw))
212
+ result = HostedIngestionDispatchV1.from_dict(document)
213
+ except (ValueError, IngestionError, TypeError) as error:
214
+ raise HostedIngestionJobError(
215
+ "INGESTION_DISPATCH_RESPONSE_INVALID",
216
+ "ingestion dispatch exchange response is not the exact accepted schema",
217
+ ) from error
218
+ if result.schema_version != HOSTED_INGESTION_DISPATCH_SCHEMA:
219
+ raise AssertionError("HostedIngestionDispatchV1 accepted an unsupported schema")
220
+ return result
221
+ finally:
222
+ raw[:] = b"\x00" * len(raw)
223
+
224
+
225
+ @dataclass(frozen=True)
226
+ class DeliveryDeadline:
227
+ """A monotonic checkpoint boundary derived from Studio's absolute UTC deadline."""
228
+
229
+ deadline: datetime
230
+ wall_clock: Callable[[], datetime] = lambda: datetime.now(UTC)
231
+ reserve_seconds: float = CHECKPOINT_RESERVE_SECONDS
232
+
233
+ def __post_init__(self) -> None:
234
+ if self.deadline.tzinfo is None:
235
+ raise ValueError("deadline must be timezone aware")
236
+ if not 0 < self.reserve_seconds < MAX_DELIVERY_DEADLINE_SECONDS:
237
+ raise ValueError("checkpoint reserve is invalid")
238
+
239
+ @classmethod
240
+ def from_dispatch(
241
+ cls,
242
+ dispatch: HostedIngestionDispatchV1,
243
+ *,
244
+ wall_clock: Callable[[], datetime] = lambda: datetime.now(UTC),
245
+ reserve_seconds: float = CHECKPOINT_RESERVE_SECONDS,
246
+ ) -> DeliveryDeadline:
247
+ deadline = parse_delivery_deadline(dispatch.delivery.deadline)
248
+ remaining = (deadline - wall_clock().astimezone(UTC)).total_seconds()
249
+ if remaining <= reserve_seconds:
250
+ raise HostedIngestionJobError(
251
+ "INGESTION_DELIVERY_EXPIRED", "delivery has no safe checkpoint window remaining"
252
+ )
253
+ if remaining > MAX_JOB_DEADLINE_SECONDS:
254
+ raise HostedIngestionJobError(
255
+ "INGESTION_DELIVERY_DEADLINE_INVALID", "delivery deadline exceeds the Job ceiling"
256
+ )
257
+ return cls(deadline=deadline, wall_clock=wall_clock, reserve_seconds=reserve_seconds)
258
+
259
+ @property
260
+ def checkpoint_at(self) -> datetime:
261
+ from datetime import timedelta
262
+
263
+ return self.deadline - timedelta(seconds=self.reserve_seconds)
264
+
265
+ def checkpoint_due(self) -> bool:
266
+ return self.wall_clock().astimezone(UTC) >= self.checkpoint_at
267
+
268
+ def require_work_time(self) -> None:
269
+ if self.checkpoint_due():
270
+ raise CheckpointRequired()
271
+
272
+
273
+ class CheckpointRequired(RuntimeError):
274
+ """Normal delivery exit: durable state must be handed back before capability expiry."""
275
+
276
+
277
+ class DeliveryProcessor(Protocol):
278
+ """A reviewed connector's bounded execution hook, intentionally not a generic URL runner."""
279
+
280
+ def __call__(
281
+ self, dispatch: HostedIngestionDispatchV1, deadline: DeliveryDeadline
282
+ ) -> DeliveryOutcome: ...
283
+
284
+
285
+ @dataclass(frozen=True)
286
+ class DeliveryOutcome:
287
+ """A connector tells Studio's future callback layer whether it finalized or checkpointed."""
288
+
289
+ status: str
290
+
291
+ def __post_init__(self) -> None:
292
+ if self.status not in {"checkpointed", "finalized"}:
293
+ raise ValueError("delivery status must be checkpointed or finalized")
294
+
295
+
296
+ def run_hosted_ingestion_delivery(
297
+ *, dispatch: HostedIngestionDispatchV1, processor: DeliveryProcessor, deadline: DeliveryDeadline
298
+ ) -> DeliveryOutcome:
299
+ """Run one reviewed processor but force a normal checkpoint exit before its hard deadline."""
300
+
301
+ try:
302
+ deadline.require_work_time()
303
+ outcome = processor(dispatch, deadline)
304
+ except CheckpointRequired:
305
+ return DeliveryOutcome("checkpointed")
306
+ if not isinstance(outcome, DeliveryOutcome):
307
+ raise HostedIngestionJobError(
308
+ "INGESTION_DELIVERY_PROCESSOR_INVALID", "connector returned no bounded delivery outcome"
309
+ )
310
+ if deadline.checkpoint_due() and outcome.status != "checkpointed":
311
+ raise HostedIngestionJobError(
312
+ "INGESTION_DELIVERY_DEADLINE_OVERRUN",
313
+ "connector returned after the checkpoint boundary without checkpointing",
314
+ )
315
+ return outcome
316
+
317
+
318
+ def ingestion_job_main(argv: Sequence[str] | None = None) -> int:
319
+ """Cloud Run entrypoint. It refuses safely until a reviewed connector is wired in."""
320
+
321
+ arguments = tuple(sys.argv[1:] if argv is None else argv)
322
+ try:
323
+ dispatch = parse_ingestion_dispatch(arguments)
324
+ except HostedIngestionJobError as error:
325
+ sys.stderr.write(f"{error.code}: {error.detail}\n")
326
+ return 2
327
+ try:
328
+ config = IngestionJobConfig.from_environment()
329
+ delivery = exchange_ingestion_dispatch(config=config, dispatch=dispatch)
330
+ DeliveryDeadline.from_dispatch(delivery)
331
+ except HostedIngestionJobError as error:
332
+ if error.code in CONSUMED_INGESTION_DISPATCH_CODES:
333
+ return 0
334
+ sys.stderr.write(f"{error.code}: {error.detail}\n")
335
+ return 1
336
+ # The deliberately closed dispatch contract has no source URL, generic callback endpoint or
337
+ # credential. A later reviewed connector/callback slice must call run_hosted_ingestion_delivery
338
+ # instead of making this entrypoint silently invent one of those authorities.
339
+ sys.stderr.write("INGESTION_DELIVERY_PROCESSOR_UNWIRED: no reviewed connector is installed\n")
340
+ return 1
341
+
342
+
343
+ __all__ = [
344
+ "CHECKPOINT_RESERVE_SECONDS",
345
+ "CONSUMED_INGESTION_DISPATCH_CODES",
346
+ "CheckpointRequired",
347
+ "DeliveryDeadline",
348
+ "DeliveryOutcome",
349
+ "HostedIngestionJobError",
350
+ "IngestionDispatch",
351
+ "IngestionJobConfig",
352
+ "exchange_ingestion_dispatch",
353
+ "ingestion_job_main",
354
+ "parse_ingestion_dispatch",
355
+ "run_hosted_ingestion_delivery",
356
+ ]
@@ -0,0 +1,40 @@
1
+ """Offline smoke for the identity-only resumable-ingestion Cloud Run Job image entrypoint."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from mostlyright.data_harness.hosted_ingestion_job import parse_ingestion_dispatch
10
+
11
+ _DISPATCH_ID = "99999999-9999-4999-8999-999999999999"
12
+ _JOB_ARGS = (
13
+ "--ingestion-dispatch-id",
14
+ _DISPATCH_ID,
15
+ "--ingestion-dispatch-exchange-url",
16
+ f"https://studio.invalid/internal/v3/ingestion/dispatches/{_DISPATCH_ID}:exchange",
17
+ "--ingestion-dispatch-audience",
18
+ "https://studio.invalid",
19
+ )
20
+
21
+
22
+ def main() -> int:
23
+ dispatch = parse_ingestion_dispatch(_JOB_ARGS)
24
+ if dispatch.dispatch_id != _DISPATCH_ID:
25
+ raise SystemExit("ingestion Job parser did not preserve the dispatch id")
26
+ entrypoint = Path(sys.executable).with_name("mr-data-ingestion-job")
27
+ result = subprocess.run(
28
+ [entrypoint, "--not-an-ingestion-job-argument"],
29
+ check=False,
30
+ capture_output=True,
31
+ timeout=10,
32
+ )
33
+ if result.returncode != 2 or b"INGESTION_DISPATCH_ARGUMENTS_INVALID" not in result.stderr:
34
+ raise SystemExit("ingestion Job entrypoint does not enforce its exact argv contract")
35
+ print("hosted-ingestion-job-smoke-ok")
36
+ return 0
37
+
38
+
39
+ if __name__ == "__main__": # pragma: no cover - executed in the built image
40
+ raise SystemExit(main())
@@ -0,0 +1,194 @@
1
+ """Linux image smoke for the production research-session parser boundaries.
2
+
3
+ This module is run inside the real session-worker image with Docker's memory ceiling set to the
4
+ exact Cloud Run value. Unit tests can exercise every refusal, but only this lane proves the image's
5
+ non-root entrypoint sees the real cgroup spelling and can install seccomp before the installed
6
+ parser execs.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import signal
13
+ import sys
14
+ import tempfile
15
+ import time
16
+ from pathlib import Path
17
+
18
+ from mostlyright.data_harness.acquisition.parsing import ParseLimits
19
+ from mostlyright.data_harness.acquisition.sandbox import (
20
+ HOSTED_SESSION_CONTAINER_MEMORY_BYTES,
21
+ _run_worker_command,
22
+ )
23
+ from mostlyright.data_harness.hosted_session_worker import SessionWorkerConfig, _default_sandbox
24
+
25
+ _ADVERSARIAL_CHILD = r"""
26
+ import ctypes
27
+ import errno
28
+ import os
29
+ import socket
30
+
31
+ status = open("/proc/self/status", encoding="ascii").read()
32
+ if "NoNewPrivs:\t1" not in status or "Seccomp:\t2" not in status:
33
+ raise SystemExit(20)
34
+
35
+ def memory_limit():
36
+ for candidate in (
37
+ "/sys/fs/cgroup/memory.max",
38
+ "/sys/fs/cgroup/memory/memory.limit_in_bytes",
39
+ ):
40
+ try:
41
+ return open(candidate, encoding="ascii").read().strip()
42
+ except OSError:
43
+ continue
44
+ raise SystemExit(21)
45
+
46
+ if memory_limit() != "4294967296":
47
+ raise SystemExit(22)
48
+ try:
49
+ open(f"/proc/{os.getppid()}/environ", "rb").read()
50
+ except PermissionError:
51
+ pass
52
+ else:
53
+ raise SystemExit(23)
54
+ membership = open("/proc/self/cgroup", encoding="ascii").read()
55
+ child = os.fork()
56
+ if child == 0:
57
+ if open("/proc/self/cgroup", encoding="ascii").read() != membership:
58
+ os._exit(30)
59
+ if memory_limit() != "4294967296":
60
+ os._exit(31)
61
+ try:
62
+ socket.socket()
63
+ except OSError as error:
64
+ os._exit(0 if error.errno == errno.EPERM else 32)
65
+ os._exit(33)
66
+ _, child_status = os.waitpid(child, 0)
67
+ if os.waitstatus_to_exitcode(child_status) != 0:
68
+ raise SystemExit(34)
69
+
70
+ libc = ctypes.CDLL(None, use_errno=True)
71
+ for number, arguments in (
72
+ (101, (0, 0, 0, 0)),
73
+ (109, (0, 0)),
74
+ (112, ()),
75
+ (272, (0,)),
76
+ (308, (-1, 0)),
77
+ (310, (os.getppid(), 0, 0, 0, 0, 0)),
78
+ (311, (os.getppid(), 0, 0, 0, 0, 0)),
79
+ (434, (os.getppid(), 0)),
80
+ (438, (-1, 0, 0)),
81
+ ):
82
+ ctypes.set_errno(0)
83
+ outcome = libc.syscall(number, *arguments)
84
+ if outcome != -1 or ctypes.get_errno() != errno.EPERM:
85
+ raise SystemExit(35)
86
+ ctypes.set_errno(0)
87
+ if libc.syscall(435, 0, 0) != -1 or ctypes.get_errno() != errno.ENOSYS:
88
+ raise SystemExit(36)
89
+ print("hosted-session-container-boundary-ok")
90
+ """
91
+
92
+ _LINGERING_DESCENDANT_CHILD = r"""
93
+ import errno
94
+ import os
95
+ import signal
96
+
97
+ descendant = os.fork()
98
+ if descendant == 0:
99
+ for detach in (os.setsid, lambda: os.setpgid(0, 0)):
100
+ try:
101
+ detach()
102
+ except OSError as error:
103
+ if error.errno != errno.EPERM:
104
+ os._exit(40)
105
+ else:
106
+ os._exit(41)
107
+ for descriptor in (0, 1, 2):
108
+ try:
109
+ os.close(descriptor)
110
+ except OSError:
111
+ pass
112
+ while True:
113
+ signal.pause()
114
+ print(descendant, flush=True)
115
+ os._exit(0)
116
+ """
117
+
118
+
119
+ def _require_descendant_reaped(pid: int) -> None:
120
+ deadline = time.monotonic() + 5.0
121
+ while time.monotonic() < deadline:
122
+ try:
123
+ waited, status = os.waitpid(pid, os.WNOHANG)
124
+ except ChildProcessError:
125
+ waited = 0
126
+ status = 0
127
+ if waited == pid:
128
+ if not os.WIFSIGNALED(status) or os.WTERMSIG(status) != signal.SIGKILL:
129
+ raise SystemExit("hosted session descendant did not die with group cleanup")
130
+ return
131
+ try:
132
+ os.kill(pid, 0)
133
+ except ProcessLookupError:
134
+ return
135
+ time.sleep(0.01)
136
+ raise SystemExit("hosted session descendant survived its request")
137
+
138
+
139
+ def main() -> int:
140
+ if sys.platform != "linux":
141
+ raise SystemExit("hosted session container smoke requires Linux")
142
+ config = SessionWorkerConfig(
143
+ studio_origin="https://studio.invalid",
144
+ lease_audience="https://studio.invalid",
145
+ warm_pool_size=0,
146
+ )
147
+ sandbox = _default_sandbox(config)
148
+ result = sandbox.parse(
149
+ request_id="session.container.smoke",
150
+ content=b"city,temp\noslo,1\n",
151
+ data_format="csv",
152
+ media_type="text/csv",
153
+ filename="source.csv",
154
+ limits=ParseLimits(),
155
+ )
156
+ if result.parsed.columns != ("city", "temp") or result.parsed.rows != (("oslo", "1"),):
157
+ raise SystemExit("installed session parser returned the wrong table")
158
+
159
+ with tempfile.TemporaryDirectory(prefix="mr-session-container-smoke-") as temporary:
160
+ stdout, stderr, returncode = _run_worker_command(
161
+ [sys.executable, "-I", "-c", _ADVERSARIAL_CHILD],
162
+ cwd=Path(temporary).resolve(),
163
+ env={"PATH": os.defpath, "PYTHONIOENCODING": "utf-8"},
164
+ request_bytes=b"",
165
+ timeout_seconds=10.0,
166
+ memory_cgroup_root_fd=None,
167
+ hosted_session_container_memory_bytes=HOSTED_SESSION_CONTAINER_MEMORY_BYTES,
168
+ )
169
+ if returncode != 0 or stderr or stdout.strip() != b"hosted-session-container-boundary-ok":
170
+ raise SystemExit("installed session child escaped or lacked its production boundaries")
171
+
172
+ with tempfile.TemporaryDirectory(prefix="mr-session-descendant-smoke-") as temporary:
173
+ stdout, stderr, returncode = _run_worker_command(
174
+ [sys.executable, "-I", "-c", _LINGERING_DESCENDANT_CHILD],
175
+ cwd=Path(temporary).resolve(),
176
+ env={"PATH": os.defpath, "PYTHONIOENCODING": "utf-8"},
177
+ request_bytes=b"",
178
+ timeout_seconds=10.0,
179
+ memory_cgroup_root_fd=None,
180
+ hosted_session_container_memory_bytes=HOSTED_SESSION_CONTAINER_MEMORY_BYTES,
181
+ )
182
+ try:
183
+ descendant_pid = int(stdout.strip())
184
+ except ValueError as error:
185
+ raise SystemExit("hosted session descendant did not report its pid") from error
186
+ if returncode != 0 or stderr:
187
+ raise SystemExit("hosted session descendant could detach before cleanup")
188
+ _require_descendant_reaped(descendant_pid)
189
+ print("hosted-session-container-smoke-ok")
190
+ return 0
191
+
192
+
193
+ if __name__ == "__main__":
194
+ raise SystemExit(main())