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,63 @@
1
+ """``mr-data login`` -- sign in to the Mostly Right cloud with a device code.
2
+
3
+ This module is the command and nothing else: its arguments and the one call that turns them into
4
+ the payload both renderings are built from. The device flow itself lives in
5
+ :mod:`mostlyright.data_harness.ux.login`, so the same flow is available to a caller that is not
6
+ going through argparse.
7
+
8
+ The only thing this command prints outside the one payload ``--json`` renders or ``_emit`` turns
9
+ into plain lines is the device code and approval address, on stderr, and only in plain mode --
10
+ matching how ``build``, ``run`` and ``recipe-run`` already print a stderr warning beside a result
11
+ that still goes through ``_emit``. ``--json`` gets none of it, so a caller asking for one
12
+ machine-readable object gets exactly one.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ from typing import Any
19
+
20
+ from mostlyright.data_harness.ux.commands import CommandSpec
21
+ from mostlyright.data_harness.ux.login import CLOUD_URL_ENV, announce_device_code, run_login
22
+
23
+ DESCRIPTION = (
24
+ "Sign in to the Mostly Right cloud from this machine. Prints a short code and opens a browser "
25
+ "to approve it there, then stores a durable device credential at ~/.mostlyright/credentials, "
26
+ f"mode 0600. The raw credential is never printed; only its prefix and last four characters "
27
+ f"are. Set {CLOUD_URL_ENV} to sign in against a different cloud."
28
+ )
29
+
30
+
31
+ def add_arguments(parser: argparse.ArgumentParser) -> None:
32
+ parser.description = DESCRIPTION
33
+ parser.add_argument(
34
+ "--device-name",
35
+ help="what to call this device in the cloud; defaults to this machine's hostname",
36
+ )
37
+ parser.add_argument(
38
+ "--force",
39
+ action="store_true",
40
+ help="compatibility alias for safe auth rotate; never truncates credentials in place",
41
+ )
42
+ parser.add_argument(
43
+ "--credential-store",
44
+ choices=("secure-file",),
45
+ help="explicitly use the secure-file credential store",
46
+ )
47
+
48
+
49
+ def run(args: argparse.Namespace) -> dict[str, Any]:
50
+ return run_login(
51
+ device_name=args.device_name,
52
+ force=args.force,
53
+ credential_store=args.credential_store,
54
+ announce=None if args.json else announce_device_code,
55
+ )
56
+
57
+
58
+ SPEC = CommandSpec(
59
+ name="login",
60
+ help="sign in to the Mostly Right cloud and store a device credential",
61
+ add_arguments=add_arguments,
62
+ run=run,
63
+ )
@@ -0,0 +1,236 @@
1
+ """``mr-data peek`` -- open any file or public https address and show what is in it.
2
+
3
+ This module is the command: its arguments, and the one call that turns them into the payload both
4
+ renderings are built from. Everything the command actually does lives in
5
+ :mod:`mostlyright.data_harness.ux.peek`, so browser rendering can use the same facts
6
+ without going through argparse.
7
+
8
+ Nothing here prints, and nothing here catches: a refusal is a typed error, and the command line
9
+ renders it with the sentences the remediation map supplies.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import re
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from mostlyright.data_harness.canonical import parse_json
20
+ from mostlyright.data_harness.readers.contracts import ReaderError, identifier, semver
21
+ from mostlyright.data_harness.ux.commands import CommandSpec
22
+ from mostlyright.data_harness.ux.peek import (
23
+ DEFAULT_SAMPLE_ROWS,
24
+ MAX_SAMPLE_ROWS,
25
+ PEEK_FORMATS,
26
+ peek_path,
27
+ peek_url,
28
+ )
29
+
30
+ # An address is a scheme followed by `://`. Requiring the slashes is what keeps a Windows path
31
+ # such as `C:\data\rows.csv` a path rather than a scheme called `c`.
32
+ _ADDRESS = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*://")
33
+
34
+
35
+ def _rows(value: str) -> int:
36
+ """Bound the row count at the command line, before anything is opened."""
37
+
38
+ try:
39
+ rows = int(value)
40
+ except ValueError:
41
+ raise argparse.ArgumentTypeError(f"{value!r} is not a whole number of rows") from None
42
+ if not 0 <= rows <= MAX_SAMPLE_ROWS:
43
+ raise argparse.ArgumentTypeError(
44
+ f"the number of rows to show must be from 0 to {MAX_SAMPLE_ROWS}"
45
+ )
46
+ return rows
47
+
48
+
49
+ def _json_column(value: str) -> tuple[str, str]:
50
+ name, separator, pointer = value.partition("=")
51
+ if not separator or not name:
52
+ raise argparse.ArgumentTypeError("must be NAME=/json/pointer (the pointer may be empty)")
53
+ return name, pointer
54
+
55
+
56
+ def _reader_coordinate(value: str) -> tuple[str, str]:
57
+ family, separator, version = value.rpartition("@")
58
+ if not separator:
59
+ raise argparse.ArgumentTypeError("must be an exact FAMILY@VERSION Reader coordinate")
60
+ try:
61
+ return identifier(family, "peek.reader.family"), semver(version, "peek.reader.version")
62
+ except ReaderError as error:
63
+ raise argparse.ArgumentTypeError(error.detail) from None
64
+
65
+
66
+ def _reader_options(value: str) -> Any:
67
+ """Parse strict JSON; the selected certified family owns the closed option shape."""
68
+
69
+ try:
70
+ return parse_json(value)
71
+ except ValueError as error:
72
+ raise argparse.ArgumentTypeError(str(error)) from None
73
+
74
+
75
+ def add_arguments(parser: argparse.ArgumentParser) -> None:
76
+ parser.add_argument(
77
+ "target",
78
+ help="a file on this machine, or an https address",
79
+ )
80
+ parser.add_argument(
81
+ "--format",
82
+ dest="data_format",
83
+ choices=PEEK_FORMATS,
84
+ default=None,
85
+ help="say what the file is when its name does not; the bytes are still checked against it",
86
+ )
87
+ parser.add_argument(
88
+ "--rows",
89
+ type=_rows,
90
+ default=DEFAULT_SAMPLE_ROWS,
91
+ help=f"how many rows to show, from 0 to {MAX_SAMPLE_ROWS} (default {DEFAULT_SAMPLE_ROWS})",
92
+ )
93
+ parser.add_argument(
94
+ "--allow-redirect-host",
95
+ dest="allow_redirect_hosts",
96
+ action="append",
97
+ default=None,
98
+ metavar="HOST",
99
+ help="also allow this host when an address redirects; repeat for more than one",
100
+ )
101
+ parser.add_argument(
102
+ "--reader",
103
+ type=_reader_coordinate,
104
+ default=None,
105
+ metavar="FAMILY@VERSION",
106
+ help="open the source with this exact certified Reader coordinate",
107
+ )
108
+ parser.add_argument(
109
+ "--reader-options",
110
+ type=_reader_options,
111
+ default=argparse.SUPPRESS,
112
+ metavar="JSON",
113
+ help=(
114
+ "strict JSON settings for --reader; the selected Reader rejects unknown or "
115
+ "invalid settings"
116
+ ),
117
+ )
118
+ parser.add_argument(
119
+ "--json-records-pointer",
120
+ default=None,
121
+ metavar="POINTER",
122
+ help="project nested JSON records selected by this RFC 6901 pointer",
123
+ )
124
+ parser.add_argument(
125
+ "--json-expand",
126
+ action="append",
127
+ default=None,
128
+ metavar="POINTER",
129
+ help="expand this array pointer in order; repeat for nested arrays",
130
+ )
131
+ parser.add_argument(
132
+ "--json-column",
133
+ action="append",
134
+ type=_json_column,
135
+ default=None,
136
+ metavar="NAME=POINTER",
137
+ help="required projected column; repeat for more columns",
138
+ )
139
+ parser.add_argument(
140
+ "--json-optional-column",
141
+ action="append",
142
+ type=_json_column,
143
+ default=None,
144
+ metavar="NAME=POINTER",
145
+ help="optional projected column; repeat for more columns",
146
+ )
147
+ parser.add_argument(
148
+ "--json-document-format",
149
+ choices=("json", "ndjson"),
150
+ default=None,
151
+ help="nested document encoding (default: json)",
152
+ )
153
+
154
+
155
+ def run(args: argparse.Namespace) -> dict[str, Any]:
156
+ target = str(args.target)
157
+ json_requested = any(
158
+ item is not None
159
+ for item in (
160
+ args.json_records_pointer,
161
+ args.json_expand,
162
+ args.json_column,
163
+ args.json_optional_column,
164
+ args.json_document_format,
165
+ )
166
+ )
167
+ reader_coordinate = args.reader
168
+ reader_options_supplied = hasattr(args, "reader_options")
169
+ reader_options = getattr(args, "reader_options", None)
170
+ if reader_options_supplied and reader_coordinate is None:
171
+ raise ReaderError(
172
+ "READER_OPTIONS",
173
+ "peek.reader_options",
174
+ "--reader-options requires an exact --reader FAMILY@VERSION",
175
+ )
176
+ if reader_options_supplied and not isinstance(reader_options, dict):
177
+ raise ReaderError(
178
+ "READER_OPTIONS",
179
+ "peek.reader_options",
180
+ "--reader-options must be a JSON object",
181
+ )
182
+ if reader_coordinate is not None and args.data_format is not None:
183
+ raise ReaderError(
184
+ "READER_OPTIONS",
185
+ "peek.format",
186
+ "--format is for direct tabular preview and cannot be combined with --reader",
187
+ )
188
+ if reader_coordinate is not None and json_requested:
189
+ raise ReaderError(
190
+ "READER_OPTIONS",
191
+ "peek.reader",
192
+ "--reader cannot be combined with the JSON projection shorthand flags",
193
+ )
194
+ if reader_coordinate is not None and not reader_options_supplied:
195
+ reader_options = {}
196
+ if json_requested:
197
+ reader_coordinate = ("json.tabular", "1.0.0")
198
+ reader_options = {
199
+ "document_format": args.json_document_format or "json",
200
+ "records_pointer": args.json_records_pointer or "",
201
+ "expand": list(args.json_expand or ()),
202
+ "columns": [
203
+ {"name": name, "pointer": pointer, "required": required}
204
+ for required, items in (
205
+ (True, args.json_column or ()),
206
+ (False, args.json_optional_column or ()),
207
+ )
208
+ for name, pointer in items
209
+ ],
210
+ }
211
+ if _ADDRESS.match(target):
212
+ result = peek_url(
213
+ target,
214
+ allow_redirect_hosts=tuple(args.allow_redirect_hosts or ()),
215
+ data_format=args.data_format,
216
+ sample_rows=args.rows,
217
+ reader_coordinate=reader_coordinate,
218
+ reader_options=reader_options,
219
+ )
220
+ else:
221
+ result = peek_path(
222
+ Path(target),
223
+ data_format=args.data_format,
224
+ sample_rows=args.rows,
225
+ reader_coordinate=reader_coordinate,
226
+ reader_options=reader_options,
227
+ )
228
+ return result.to_dict()
229
+
230
+
231
+ SPEC = CommandSpec(
232
+ name="peek",
233
+ help="open any file or public https address and show its columns, types, and first rows",
234
+ add_arguments=add_arguments,
235
+ run=run,
236
+ )
@@ -0,0 +1,90 @@
1
+ """``mr-data plan-check`` -- try a plan by building it, into a folder that is thrown away after.
2
+
3
+ This module is the command and nothing else: its arguments, and the one call that turns them into
4
+ the payload both renderings are built from. The build itself lives in
5
+ :mod:`mostlyright.data_harness.ux.plan_check`, so the same answer is available to a browser page
6
+ without going through argparse.
7
+
8
+ The engines offered here are the same closed set ``build`` offers, imported from
9
+ :mod:`mostlyright.data_harness.pipeline` rather than restated, so the two cannot drift into
10
+ disagreeing about what this machine can build with.
11
+
12
+ Nothing here prints, and nothing here catches: a plan that does not build is refused in exactly the
13
+ words the real build refuses it in, and the command line renders that refusal with the sentences
14
+ the error-remediation map supplies.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ from mostlyright.data_harness.pipeline import (
24
+ _VERIFY_ALLOWLISTED_ENGINES,
25
+ REFERENCE_BACKEND_NAME,
26
+ )
27
+ from mostlyright.data_harness.ux.commands import CommandSpec
28
+ from mostlyright.data_harness.ux.plan_check import check_plan
29
+
30
+ # Said in the command's own help, not only in a design document. Both halves matter: it really
31
+ # builds, so what it says is true; and it really deletes, so it can be run again and again.
32
+ DESCRIPTION = (
33
+ "Build this plan into a folder that is deleted afterwards, to find out whether it works "
34
+ "before you commit to it. This is the same build the build command runs, with nothing "
35
+ "skipped and no limit relaxed, so a plan that fails here would have failed there. The data "
36
+ "fingerprint it reports is the one your real build will produce; the build's own fingerprint "
37
+ "is not reported, because a dry run is not run by anybody."
38
+ )
39
+
40
+
41
+ def add_arguments(parser: argparse.ArgumentParser) -> None:
42
+ parser.description = DESCRIPTION
43
+ parser.add_argument(
44
+ "plan",
45
+ type=Path,
46
+ help="the plan to try",
47
+ )
48
+ parser.add_argument(
49
+ "--input-root",
50
+ type=Path,
51
+ required=True,
52
+ help="the folder your sources are read from",
53
+ )
54
+ parser.add_argument(
55
+ "--engine",
56
+ choices=sorted(_VERIFY_ALLOWLISTED_ENGINES),
57
+ default=REFERENCE_BACKEND_NAME,
58
+ help="which engine to build with; the same choices the build command takes",
59
+ )
60
+ parser.add_argument(
61
+ "--keep",
62
+ action="store_true",
63
+ help=(
64
+ "leave the folder it built into in place and name it, for looking at a failure; "
65
+ "run chmod -R u+w on it before removing it"
66
+ ),
67
+ )
68
+
69
+
70
+ def run(args: argparse.Namespace) -> dict[str, Any]:
71
+ # Imported here, not at the top: this package is loaded while the command line is still being
72
+ # set up. Reading the plan through the command line's own reader is what keeps a malformed
73
+ # plan reading the same here as it does everywhere else.
74
+ from mostlyright.data_harness import cli
75
+
76
+ result = check_plan(
77
+ cli._read_json(args.plan),
78
+ input_root=args.input_root,
79
+ engine=args.engine,
80
+ keep=args.keep,
81
+ )
82
+ return result.to_dict()
83
+
84
+
85
+ SPEC = CommandSpec(
86
+ name="plan-check",
87
+ help="build this plan into a throwaway folder to see whether it works, then throw it away",
88
+ add_arguments=add_arguments,
89
+ run=run,
90
+ )
@@ -0,0 +1,97 @@
1
+ """``mr-data preflight`` -- what would stop a build here, before you start one.
2
+
3
+ This module is the command and nothing else: its arguments, the one call that turns them into the
4
+ payload both renderings are built from, and the exit code a script can act on. The checks live in
5
+ :mod:`mostlyright.data_harness.ux.preflight`, so the same answer is available to a browser page
6
+ without going through argparse.
7
+
8
+ Every argument is optional. With none of them the three platform checks still run, because those
9
+ are the ones that cost a whole build to discover the hard way.
10
+
11
+ ``--workload`` is the argument that asks about the *job* rather than about the machine: whether
12
+ this plan's own sources fit the ceilings the engine holds whatever it is run on. It costs one
13
+ ``lstat`` per source and opens none of them, and it is the answer that otherwise arrives as a
14
+ container's ``MemoryError`` after the upload and the approval.
15
+
16
+ The exit code is 1 when anything is blocking, so ``mr-data preflight && mr-data build ...`` does
17
+ what it looks like it does. Nothing is refused and nothing is raised: a machine that cannot build
18
+ is an answer this command was asked for, and it is printed in full either way.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ from collections.abc import Mapping
25
+ from pathlib import Path
26
+ from typing import Any
27
+
28
+ from mostlyright.data_harness.pipeline import _VERIFY_ALLOWLISTED_ENGINES
29
+ from mostlyright.data_harness.ux.commands import CommandSpec
30
+ from mostlyright.data_harness.ux.preflight import BLOCKED_KEY, run_preflight
31
+
32
+ DESCRIPTION = (
33
+ "Check what would stop a build before you start one. With no arguments it reports what this "
34
+ "machine itself can and cannot do; name a workbench folder, an input folder, a path to write "
35
+ "to, or an engine and each of those is checked too. Name a workload -- a workbench folder, a "
36
+ "plan, or a recipe -- and the job itself is measured against the ceilings the engine will "
37
+ "judge it by, so a job that cannot run is refused here rather than inside a container after "
38
+ "its sources have been uploaded. Nothing is built, nothing is written, no source is opened "
39
+ "and nothing is sent anywhere. The exit code is 1 when something would stop you."
40
+ )
41
+
42
+
43
+ def add_arguments(parser: argparse.ArgumentParser) -> None:
44
+ parser.description = DESCRIPTION
45
+ parser.add_argument(
46
+ "--workspace",
47
+ type=Path,
48
+ help="a workbench folder to report the stage of",
49
+ )
50
+ parser.add_argument(
51
+ "--input-root",
52
+ type=Path,
53
+ help="the folder your sources would be read from",
54
+ )
55
+ parser.add_argument(
56
+ "--output",
57
+ type=Path,
58
+ help="the path a build would be written to",
59
+ )
60
+ parser.add_argument(
61
+ "--engine",
62
+ choices=sorted(_VERIFY_ALLOWLISTED_ENGINES),
63
+ help="which engine you mean to build with; the same choices the build command takes",
64
+ )
65
+ parser.add_argument(
66
+ "--workload",
67
+ type=Path,
68
+ help=(
69
+ "the job to measure: a workbench folder, a plan, or a recipe. A workbench folder "
70
+ "already holds its sources; a plan or a recipe names them relative to --input-root"
71
+ ),
72
+ )
73
+
74
+
75
+ def run(args: argparse.Namespace) -> dict[str, Any]:
76
+ return run_preflight(
77
+ workspace=args.workspace,
78
+ input_root=args.input_root,
79
+ output=args.output,
80
+ engine=args.engine,
81
+ workload=args.workload,
82
+ ).to_dict()
83
+
84
+
85
+ def exit_code(payload: Mapping[str, Any]) -> int:
86
+ """1 when something would stop a build, so this reads as a gate in a script."""
87
+
88
+ return 1 if payload.get(BLOCKED_KEY) else 0
89
+
90
+
91
+ SPEC = CommandSpec(
92
+ name="preflight",
93
+ help="check what would stop a build before you start it",
94
+ add_arguments=add_arguments,
95
+ run=run,
96
+ exit_code=exit_code,
97
+ )
@@ -0,0 +1,107 @@
1
+ """``mr-data record`` -- turn recorded live messages into sealed batches a build can read.
2
+
3
+ A live connection can never be replayed, so a build never holds one. Capture is a separate
4
+ job: it takes the messages a venue sent, groups them into bounded batches, seals each batch
5
+ under the digest of its own bytes, and writes down what arrived on which connection. A build
6
+ then reads those sealed batches and reaches nothing at all.
7
+
8
+ This command is the second half of that job. It does not connect to anything. It reads
9
+ messages somebody already recorded, applies the venue's own rules for grouping and naming them,
10
+ and writes the sealed result. Opening the connection belongs to a trusted role that does not
11
+ exist here yet, and this build reaches no network anywhere.
12
+
13
+ Nothing here needs a clock. Every time in the sealed result comes out of the recording, so the
14
+ same recording always seals to the same bytes and the same names, on any machine, on any day.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ from mostlyright.data_harness.sources.stream_connector import (
24
+ STREAM_CONNECTOR_IDS,
25
+ stream_connector,
26
+ )
27
+ from mostlyright.data_harness.sources.stream_recorder import (
28
+ capture_root,
29
+ seal_recorded_capture,
30
+ )
31
+ from mostlyright.data_harness.ux.commands import CommandSpec
32
+
33
+ DESCRIPTION = (
34
+ "Turn messages already recorded from a live source into sealed batches a build can read. "
35
+ "This command connects to nothing: you point it at a file of recorded messages and at a "
36
+ "folder to write into, and it groups, names and seals them under the chosen source's own "
37
+ "rules. Every dropped connection is written down as one, because a build that spans a hole "
38
+ "in what was captured has to be able to see the hole."
39
+ )
40
+
41
+ CAPTURE_RECORD_VERSION = "mr-data-stream-capture.v1"
42
+
43
+
44
+ def add_arguments(parser: argparse.ArgumentParser) -> None:
45
+ parser.description = DESCRIPTION
46
+ parser.add_argument(
47
+ "--connector",
48
+ choices=STREAM_CONNECTOR_IDS,
49
+ required=True,
50
+ help="which live source these messages came from",
51
+ )
52
+ parser.add_argument(
53
+ "--replay",
54
+ type=Path,
55
+ required=True,
56
+ help="the file of recorded messages to seal, one JSON object per line",
57
+ )
58
+ parser.add_argument(
59
+ "--output",
60
+ type=Path,
61
+ required=True,
62
+ help="the folder to write the sealed batches into",
63
+ )
64
+
65
+
66
+ def run(args: argparse.Namespace) -> dict[str, Any]:
67
+ # Imported here, not at the top: this package is loaded while the command line is still
68
+ # being set up, and the boundary module is what sets it up.
69
+ from mostlyright.data_harness import cli
70
+
71
+ config = stream_connector(args.connector)
72
+ recording = cli._read_bytes(args.replay)
73
+ captures_root = args.output.absolute()
74
+ manifest = seal_recorded_capture(
75
+ config=config,
76
+ recording=recording,
77
+ captures_root=captures_root,
78
+ )
79
+ return {
80
+ "schema_version": CAPTURE_RECORD_VERSION,
81
+ "status": "stream_recorded",
82
+ "connector_id": manifest.connector_id,
83
+ "connector_config_digest": manifest.connector_config_digest,
84
+ "stream_id": manifest.stream_id,
85
+ "capture_digest": manifest.digest,
86
+ "capture_folder": str(capture_root(captures_root, manifest.stream_id)),
87
+ "data_format": manifest.data_format,
88
+ "batch_filename": manifest.batch_filename,
89
+ "connection_count": len(manifest.epochs),
90
+ "recorded_gap_count": manifest.gap_count,
91
+ "kept_message_count": manifest.admitted_event_count,
92
+ "skipped_message_count": manifest.ignored_message_count,
93
+ "batch_count": len(manifest.batches),
94
+ "sealed_batch_names": [batch.storage_name for batch in manifest.batches],
95
+ "bookmarks": [batch.next_bookmark for batch in manifest.batches],
96
+ "recorded_through": manifest.recorded_through,
97
+ "network_fetch_performed": False,
98
+ "live_connection_opened": False,
99
+ }
100
+
101
+
102
+ SPEC = CommandSpec(
103
+ name="record",
104
+ help="seal already-recorded live messages into batches a build can read",
105
+ add_arguments=add_arguments,
106
+ run=run,
107
+ )
@@ -0,0 +1,47 @@
1
+ """``mr-data review-setup`` -- create protected material for independent local review."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from mostlyright.data_harness.ux.commands import CommandSpec
10
+
11
+
12
+ def add_arguments(parser: argparse.ArgumentParser) -> None:
13
+ parser.description = (
14
+ "Create protected operator material for one sealed Build. This setup does not approve "
15
+ "the Build and does not mint a release attestation."
16
+ )
17
+ parser.add_argument("workspace", type=Path, help="the work folder containing the sealed Build")
18
+ parser.add_argument(
19
+ "--operator-root",
20
+ type=Path,
21
+ required=True,
22
+ help="a new protected folder for enrollment, private keys, and role tokens",
23
+ )
24
+ parser.add_argument(
25
+ "--anchor-output",
26
+ type=Path,
27
+ required=True,
28
+ help="a new file to install separately at the coordinator's fixed trust-anchor path",
29
+ )
30
+
31
+
32
+ def run(args: argparse.Namespace) -> dict[str, Any]:
33
+ from mostlyright.data_harness.operator_setup import provision_review_setup
34
+
35
+ return provision_review_setup(
36
+ args.workspace,
37
+ operator_root=args.operator_root,
38
+ anchor_path=args.anchor_output,
39
+ ).receipt()
40
+
41
+
42
+ SPEC = CommandSpec(
43
+ name="review-setup",
44
+ help="create protected review material; this does not approve a Build",
45
+ add_arguments=add_arguments,
46
+ run=run,
47
+ )