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
+ """Bounded, direct Cloud v1 device-key management client.
2
+
3
+ The Cloud service is authoritative for device-key liveness and identity. This module only
4
+ projects a fixed public allowlist from its responses; it never treats local metadata as authority
5
+ and never includes a credential, raw response, header, or exception representation in an error.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import ssl
12
+ import urllib.error
13
+ import urllib.parse
14
+ import urllib.request
15
+ from collections.abc import Callable, Mapping
16
+ from dataclasses import dataclass
17
+ from typing import Any, Final
18
+
19
+ REQUEST_TIMEOUT_SECONDS: Final = 10.0
20
+ MAX_RESPONSE_BYTES: Final = 64 * 1024
21
+ MAX_KEY_ID_CHARS: Final = 256
22
+ _ROLE_VALUES: Final = frozenset({"owner", "admin", "editor", "viewer"})
23
+
24
+
25
+ class CloudAuthError(RuntimeError):
26
+ """A stable, credential-free client outcome."""
27
+
28
+ def __init__(self, code: str, detail: str, *, retry_after_seconds: int | None = None) -> None:
29
+ super().__init__(f"{code}: {detail}")
30
+ self.code = code
31
+ self.detail = detail
32
+ self.retry_after_seconds = retry_after_seconds
33
+
34
+ def __repr__(self) -> str:
35
+ return (
36
+ "CloudAuthError("
37
+ f"code={self.code!r}, detail={self.detail!r}, "
38
+ f"retry_after_seconds={self.retry_after_seconds!r})"
39
+ )
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class CloudAuthResponse:
44
+ """A bounded response seam used by both urllib and isolated tests."""
45
+
46
+ status: int
47
+ headers: Mapping[str, str]
48
+ body: bytes
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class DeviceMetadata:
53
+ """Display-safe device metadata projected from the Cloud allowlist."""
54
+
55
+ key_id: str
56
+ name: str
57
+ prefix: str
58
+ last_four: str
59
+ created_at: str
60
+ last_used_at: str | None
61
+ current: bool | None = None
62
+
63
+ def to_receipt(self) -> dict[str, str | bool | None]:
64
+ receipt: dict[str, str | bool | None] = {
65
+ "id": self.key_id,
66
+ "name": self.name,
67
+ "prefix": self.prefix,
68
+ "last_four": self.last_four,
69
+ "created_at": self.created_at,
70
+ "last_used_at": self.last_used_at,
71
+ }
72
+ if self.current is not None:
73
+ receipt["current"] = self.current
74
+ return receipt
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class CloudIdentity:
79
+ organization_id: str
80
+ user_id: str
81
+ role: str
82
+
83
+ def to_receipt(self) -> dict[str, str]:
84
+ return {
85
+ "organization_id": self.organization_id,
86
+ "user_id": self.user_id,
87
+ "role": self.role,
88
+ }
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class StudioIdentity:
93
+ workspace_id: str
94
+ principal_id: str
95
+
96
+ def to_receipt(self) -> dict[str, str]:
97
+ return {"workspace_id": self.workspace_id, "principal_id": self.principal_id}
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class CurrentIdentity:
102
+ cloud: CloudIdentity
103
+ studio: StudioIdentity | None
104
+
105
+ def to_receipt(self) -> dict[str, Any]:
106
+ return {
107
+ "cloud": self.cloud.to_receipt(),
108
+ "studio": None if self.studio is None else self.studio.to_receipt(),
109
+ }
110
+
111
+
112
+ @dataclass(frozen=True)
113
+ class CurrentDevice:
114
+ device: DeviceMetadata
115
+ identity: CurrentIdentity
116
+
117
+ def to_receipt(self) -> dict[str, Any]:
118
+ return {"device": self.device.to_receipt(), "identity": self.identity.to_receipt()}
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class RevokeProof:
123
+ """One exact remote proof; callers still own every local state transition."""
124
+
125
+ status: str
126
+
127
+ def to_receipt(self) -> dict[str, str]:
128
+ return {"status": self.status}
129
+
130
+
131
+ Transport = Callable[[urllib.request.Request, float], CloudAuthResponse]
132
+
133
+
134
+ class _NoRedirect(urllib.request.HTTPRedirectHandler):
135
+ """Do not let an auth header cross an unreviewed redirect boundary."""
136
+
137
+ def redirect_request(self, *args: Any, **kwargs: Any) -> None:
138
+ del args, kwargs
139
+ return None
140
+
141
+
142
+ class _ObjectPairs(list[tuple[str, Any]]):
143
+ """Marks JSON objects so a top-level array cannot imitate an object pair list."""
144
+
145
+
146
+ class CloudAuthClient:
147
+ """The exact v1 current/list/delete contract, over direct HTTPS only."""
148
+
149
+ def __init__(
150
+ self,
151
+ cloud_url: str,
152
+ raw_key: str,
153
+ *,
154
+ transport: Transport | None = None,
155
+ ) -> None:
156
+ self._base_url = _validated_base_url(cloud_url)
157
+ if not isinstance(raw_key, str) or not raw_key:
158
+ raise CloudAuthError(
159
+ "CLOUD_AUTH_CREDENTIAL_INVALID", "the device credential is invalid"
160
+ )
161
+ self._raw_key = raw_key
162
+ self._transport = transport or _urllib_transport
163
+
164
+ def __repr__(self) -> str:
165
+ return f"CloudAuthClient(cloud_url={self._base_url!r}, raw_key='<redacted>')"
166
+
167
+ def current(self) -> CurrentDevice:
168
+ """Return the allowlisted current device and remotely authoritative identity."""
169
+
170
+ response = self._request("GET", "/api/cli/v1/device-keys/current")
171
+ value = _json_object(response.body)
172
+ _require_api_version(value)
173
+ device = _device_metadata(value.get("deviceKey"))
174
+ identity_value = value.get("identity")
175
+ if not isinstance(identity_value, dict):
176
+ raise _contract_error()
177
+ cloud_value = identity_value.get("cloud")
178
+ if not isinstance(cloud_value, dict):
179
+ raise _contract_error()
180
+ organization_id = _required_text(cloud_value, "organizationId")
181
+ user_id = _required_text(cloud_value, "userId")
182
+ role = _required_text(cloud_value, "role")
183
+ if role not in _ROLE_VALUES:
184
+ raise _contract_error()
185
+ studio_value = identity_value.get("studio")
186
+ if studio_value is None:
187
+ studio = None
188
+ elif isinstance(studio_value, dict):
189
+ # Both fields must be present together. Any additional remote data stays remote.
190
+ studio = StudioIdentity(
191
+ workspace_id=_required_text(studio_value, "workspaceId"),
192
+ principal_id=_required_text(studio_value, "principalId"),
193
+ )
194
+ else:
195
+ raise _contract_error()
196
+ return CurrentDevice(
197
+ device=device,
198
+ identity=CurrentIdentity(
199
+ cloud=CloudIdentity(
200
+ organization_id=organization_id,
201
+ user_id=user_id,
202
+ role=role,
203
+ ),
204
+ studio=studio,
205
+ ),
206
+ )
207
+
208
+ def list_devices(self) -> tuple[DeviceMetadata, ...]:
209
+ """Return Cloud-ordered metadata only; no local capability is inferred."""
210
+
211
+ response = self._request("GET", "/api/cli/v1/device-keys")
212
+ value = _json_object(response.body)
213
+ _require_api_version(value)
214
+ device_keys = value.get("deviceKeys")
215
+ if not isinstance(device_keys, list):
216
+ raise _contract_error()
217
+ return tuple(_device_metadata(item, require_current=True) for item in device_keys)
218
+
219
+ def revoke(
220
+ self,
221
+ key_id: str,
222
+ *,
223
+ exact_secret_same_key_replay: bool = False,
224
+ ) -> RevokeProof:
225
+ """Prove only Cloud's exact DELETE acknowledgement.
226
+
227
+ ``already_revoked`` is meaningful solely when the caller retained the raw secret for the
228
+ very same key and explicitly marks that recovery condition. The client does not delete
229
+ anything locally.
230
+ """
231
+
232
+ path_segment = _path_segment(key_id)
233
+ response = self._request("DELETE", f"/api/cli/v1/device-keys/{path_segment}")
234
+ proof = _exact_revoke_proof(response.body)
235
+ if proof.status == "already_revoked" and not exact_secret_same_key_replay:
236
+ raise _contract_error()
237
+ return proof
238
+
239
+ def _request(self, method: str, path: str) -> CloudAuthResponse:
240
+ request = urllib.request.Request(
241
+ urllib.parse.urljoin(self._base_url + "/", path.lstrip("/")),
242
+ headers={"x-api-key": self._raw_key},
243
+ method=method,
244
+ )
245
+ try:
246
+ response = self._transport(request, REQUEST_TIMEOUT_SECONDS)
247
+ except urllib.error.HTTPError as error:
248
+ response = CloudAuthResponse(
249
+ status=error.code,
250
+ headers={str(key): str(value) for key, value in error.headers.items()},
251
+ body=b"",
252
+ )
253
+ except (urllib.error.URLError, TimeoutError, ssl.SSLError, OSError, ValueError):
254
+ raise CloudAuthError(
255
+ "CLOUD_AUTH_TRANSPORT_FAILED", "Cloud could not be reached securely"
256
+ ) from None
257
+ if not isinstance(response, CloudAuthResponse):
258
+ raise CloudAuthError("CLOUD_AUTH_TRANSPORT_FAILED", "Cloud returned no usable response")
259
+ _bounded_response(response)
260
+ mapped = _remote_error(response)
261
+ if mapped is not None:
262
+ raise mapped
263
+ if response.status != 200:
264
+ raise CloudAuthError(
265
+ "CLOUD_AUTH_UNEXPECTED_STATUS", "Cloud returned an unexpected status"
266
+ )
267
+ if not _is_json_media_type(response.headers):
268
+ raise _contract_error()
269
+ return response
270
+
271
+
272
+ def _validated_base_url(value: str) -> str:
273
+ if not isinstance(value, str) or len(value) > 2048:
274
+ raise CloudAuthError("CLOUD_AUTH_URL_INVALID", "Cloud URL is invalid")
275
+ parsed = urllib.parse.urlsplit(value)
276
+ if (
277
+ parsed.scheme != "https"
278
+ or not parsed.hostname
279
+ or parsed.username is not None
280
+ or parsed.password is not None
281
+ or parsed.query
282
+ or parsed.fragment
283
+ ):
284
+ raise CloudAuthError("CLOUD_AUTH_URL_INVALID", "Cloud URL is invalid")
285
+ try:
286
+ _ = parsed.port
287
+ except ValueError:
288
+ raise CloudAuthError("CLOUD_AUTH_URL_INVALID", "Cloud URL is invalid") from None
289
+ return urllib.parse.urlunsplit((parsed.scheme, parsed.netloc, parsed.path.rstrip("/"), "", ""))
290
+
291
+
292
+ def _urllib_transport(request: urllib.request.Request, timeout: float) -> CloudAuthResponse:
293
+ opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), _NoRedirect())
294
+ with opener.open(request, timeout=timeout) as response:
295
+ headers = {str(key): str(value) for key, value in response.headers.items()}
296
+ content_length = headers.get("Content-Length")
297
+ if content_length is not None:
298
+ try:
299
+ if int(content_length) > MAX_RESPONSE_BYTES:
300
+ raise _contract_error()
301
+ except ValueError:
302
+ raise _contract_error() from None
303
+ body = response.read(MAX_RESPONSE_BYTES + 1)
304
+ return CloudAuthResponse(status=response.status, headers=headers, body=body)
305
+
306
+
307
+ def _bounded_response(response: CloudAuthResponse) -> None:
308
+ if not isinstance(response.status, int) or isinstance(response.status, bool):
309
+ raise CloudAuthError("CLOUD_AUTH_TRANSPORT_FAILED", "Cloud returned no usable response")
310
+ if not isinstance(response.body, bytes) or len(response.body) > MAX_RESPONSE_BYTES:
311
+ raise _contract_error()
312
+ if not isinstance(response.headers, Mapping):
313
+ raise CloudAuthError("CLOUD_AUTH_TRANSPORT_FAILED", "Cloud returned no usable response")
314
+
315
+
316
+ def _remote_error(response: CloudAuthResponse) -> CloudAuthError | None:
317
+ code = {
318
+ 401: "CLOUD_AUTH_UNAUTHORIZED",
319
+ 403: "CLOUD_AUTH_FORBIDDEN",
320
+ 404: "CLOUD_AUTH_NOT_FOUND",
321
+ 429: "CLOUD_AUTH_RATE_LIMITED",
322
+ 503: "CLOUD_AUTH_TEMPORARILY_UNAVAILABLE",
323
+ }.get(response.status)
324
+ if code is None:
325
+ return None
326
+ retry_after = _retry_after(response.headers) if response.status in {429, 503} else None
327
+ details = {
328
+ "CLOUD_AUTH_UNAUTHORIZED": "Cloud did not accept this device credential",
329
+ "CLOUD_AUTH_FORBIDDEN": "Cloud did not authorize this device operation",
330
+ "CLOUD_AUTH_NOT_FOUND": "Cloud did not find that device operation",
331
+ "CLOUD_AUTH_RATE_LIMITED": "Cloud temporarily rate limited this device operation",
332
+ "CLOUD_AUTH_TEMPORARILY_UNAVAILABLE": "Cloud is temporarily unavailable",
333
+ }
334
+ return CloudAuthError(code, details[code], retry_after_seconds=retry_after)
335
+
336
+
337
+ def _retry_after(headers: Mapping[str, str]) -> int | None:
338
+ value = next((item for key, item in headers.items() if key.lower() == "retry-after"), None)
339
+ if not isinstance(value, str) or not value.isascii() or not value.isdecimal():
340
+ return None
341
+ parsed = int(value)
342
+ return parsed if 0 <= parsed <= 86_400 else None
343
+
344
+
345
+ def _is_json_media_type(headers: Mapping[str, str]) -> bool:
346
+ value = next((item for key, item in headers.items() if key.lower() == "content-type"), None)
347
+ return isinstance(value, str) and value.split(";", 1)[0].strip().lower() == "application/json"
348
+
349
+
350
+ def _json_object(body: bytes) -> dict[str, Any]:
351
+ try:
352
+ value = json.loads(body.decode("utf-8"))
353
+ except (UnicodeDecodeError, json.JSONDecodeError):
354
+ raise _contract_error() from None
355
+ if not isinstance(value, dict):
356
+ raise _contract_error()
357
+ return value
358
+
359
+
360
+ def _require_api_version(value: Mapping[str, Any]) -> None:
361
+ if value.get("apiVersion") != "1":
362
+ raise _contract_error()
363
+
364
+
365
+ def _required_text(value: Mapping[str, Any], field: str) -> str:
366
+ item = value.get(field)
367
+ if not _safe_text(item, required=True):
368
+ raise _contract_error()
369
+ return item
370
+
371
+
372
+ def _safe_text(value: object, *, required: bool) -> bool:
373
+ return (
374
+ isinstance(value, str)
375
+ and (bool(value) if required else True)
376
+ and len(value) <= 4096
377
+ and not any(ord(character) < 0x20 or ord(character) == 0x7F for character in value)
378
+ )
379
+
380
+
381
+ def _device_metadata(value: Any, *, require_current: bool = False) -> DeviceMetadata:
382
+ if not isinstance(value, dict):
383
+ raise _contract_error()
384
+ if "lastUsedAt" not in value:
385
+ raise _contract_error()
386
+ last_used_at = value["lastUsedAt"]
387
+ if last_used_at is not None and not _safe_text(last_used_at, required=False):
388
+ raise _contract_error()
389
+ current = value.get("current")
390
+ if require_current and not isinstance(current, bool):
391
+ raise _contract_error()
392
+ return DeviceMetadata(
393
+ key_id=_required_text(value, "id"),
394
+ name=_required_text(value, "name"),
395
+ prefix=_required_text(value, "prefix"),
396
+ last_four=_required_text(value, "lastFour"),
397
+ created_at=_required_text(value, "createdAt"),
398
+ last_used_at=last_used_at,
399
+ current=current if require_current else None,
400
+ )
401
+
402
+
403
+ def _path_segment(key_id: str) -> str:
404
+ if (
405
+ not isinstance(key_id, str)
406
+ or not key_id
407
+ or len(key_id) > MAX_KEY_ID_CHARS
408
+ or any(ord(character) < 0x20 or ord(character) == 0x7F for character in key_id)
409
+ ):
410
+ raise CloudAuthError("CLOUD_AUTH_KEY_ID_INVALID", "device key id is invalid")
411
+ return urllib.parse.quote(key_id, safe="")
412
+
413
+
414
+ def _exact_revoke_proof(body: bytes) -> RevokeProof:
415
+ try:
416
+ value = json.loads(body.decode("utf-8"), object_pairs_hook=_ObjectPairs)
417
+ except (UnicodeDecodeError, json.JSONDecodeError):
418
+ raise _contract_error() from None
419
+ if not isinstance(value, _ObjectPairs) or len(value) != 2:
420
+ raise _contract_error()
421
+ keys = [key for key, _item in value]
422
+ if set(keys) != {"apiVersion", "status"} or len(set(keys)) != 2:
423
+ raise _contract_error()
424
+ fields = dict(value)
425
+ if fields.get("apiVersion") != "1" or fields.get("status") not in {
426
+ "revoked",
427
+ "already_revoked",
428
+ }:
429
+ raise _contract_error()
430
+ return RevokeProof(status=fields["status"])
431
+
432
+
433
+ def _contract_error() -> CloudAuthError:
434
+ return CloudAuthError("CLOUD_AUTH_CONTRACT_INVALID", "Cloud returned an invalid auth contract")
435
+
436
+
437
+ __all__ = [
438
+ "CloudAuthClient",
439
+ "CloudAuthError",
440
+ "CloudAuthResponse",
441
+ "CloudIdentity",
442
+ "CurrentDevice",
443
+ "CurrentIdentity",
444
+ "DeviceMetadata",
445
+ "RevokeProof",
446
+ "StudioIdentity",
447
+ ]
@@ -0,0 +1,260 @@
1
+ """Closed, lazy descriptors for the ``mr-data`` subcommands.
2
+
3
+ Inventory and top-level help read the literal descriptors below without scanning packages,
4
+ loading entry points, or importing command implementations. A selected command resolves exactly
5
+ one fixed module and verifies that module's ``SPEC`` still matches its descriptor.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import importlib
12
+ import pkgutil
13
+ from collections.abc import Callable, Iterable, Mapping, Sequence
14
+ from dataclasses import dataclass
15
+ from typing import Any
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class CommandSpec:
20
+ """One subcommand: its name, its help line, its arguments, and its payload builder.
21
+
22
+ ``run`` returns the payload mapping and nothing else. It never prints and never decides the
23
+ output mode -- the caller renders the payload as plain lines or as JSON.
24
+
25
+ ``exit_code`` is read from that same payload, for the commands whose answer is a gate a script
26
+ acts on rather than a report a person reads. It is derived from the payload rather than
27
+ returned alongside it so that the exit code cannot say anything the printed answer does not;
28
+ a command without one always succeeds when it produced a payload at all.
29
+
30
+ ``validate`` refuses an argument set ``run`` would refuse, without running it. Argparse can
31
+ only express the rules one flag states about itself; a command whose flags are required in one
32
+ mode and forbidden in another has rules argparse cannot carry, and without this hook the only
33
+ way to discover them is to execute the command. A command that declares one promises that
34
+ ``validate`` accepts exactly the namespaces ``run`` reaches its first side effect with.
35
+ """
36
+
37
+ name: str
38
+ help: str
39
+ add_arguments: Callable[[argparse.ArgumentParser], None]
40
+ run: Callable[[argparse.Namespace], dict[str, Any]]
41
+ exit_code: Callable[[Mapping[str, Any]], int] | None = None
42
+ implementation_module: str | None = None
43
+ validate: Callable[[argparse.Namespace], None] | None = None
44
+
45
+ def resolve(self) -> CommandSpec:
46
+ """Load and verify this descriptor's fixed implementation, if it is lazy."""
47
+
48
+ if self.implementation_module is None:
49
+ return self
50
+ return _load(self.implementation_module, self.name, self.help)
51
+
52
+
53
+ _LOADED: dict[str, CommandSpec] = {}
54
+
55
+
56
+ def _load(module_name: str, expected_name: str, expected_help: str) -> CommandSpec:
57
+ loaded = _LOADED.get(module_name)
58
+ if loaded is None:
59
+ module = importlib.import_module(f"{__name__}.{module_name}")
60
+ loaded = getattr(module, "SPEC", None)
61
+ if not isinstance(loaded, CommandSpec):
62
+ raise RuntimeError(f"command module {module_name!r} does not declare a CommandSpec")
63
+ if loaded.name != expected_name or loaded.help != expected_help:
64
+ raise RuntimeError(
65
+ f"command descriptor for {expected_name!r} disagrees with module {module_name!r}"
66
+ )
67
+ _LOADED[module_name] = loaded
68
+ return loaded
69
+
70
+
71
+ def _lazy(module_name: str, name: str, help_text: str) -> CommandSpec:
72
+ def add_arguments(parser: argparse.ArgumentParser) -> None:
73
+ _load(module_name, name, help_text).add_arguments(parser)
74
+
75
+ def run(args: argparse.Namespace) -> dict[str, Any]:
76
+ return _load(module_name, name, help_text).run(args)
77
+
78
+ def exit_code(payload: Mapping[str, Any]) -> int:
79
+ implementation = _load(module_name, name, help_text).exit_code
80
+ return 0 if implementation is None else implementation(payload)
81
+
82
+ def validate(args: argparse.Namespace) -> None:
83
+ implementation = _load(module_name, name, help_text).validate
84
+ if implementation is not None:
85
+ implementation(args)
86
+
87
+ return CommandSpec(
88
+ name=name,
89
+ help=help_text,
90
+ add_arguments=add_arguments,
91
+ run=run,
92
+ exit_code=exit_code,
93
+ implementation_module=module_name,
94
+ validate=validate,
95
+ )
96
+
97
+
98
+ _DISCOVERED: tuple[CommandSpec, ...] = (
99
+ _lazy(
100
+ "approve",
101
+ "approve",
102
+ "freeze one recipe by fingerprint, after showing you exactly what you are freezing",
103
+ ),
104
+ _lazy(
105
+ "auth",
106
+ "auth",
107
+ "inspect remotely validated device authority and token boundaries",
108
+ ),
109
+ _lazy(
110
+ "author",
111
+ "author",
112
+ "turn a YAML recipe description into a recipe you can approve",
113
+ ),
114
+ # The provider-scale workflow is three verbs -- harvest, then author, then publish -- and each
115
+ # one is a separate mutation with a receipt of its own: `catalog-harvest` is the only one that
116
+ # reaches a network or reads a key, and it is also the only one that can be asked for the key
117
+ # check on its own. The descriptor tuple stays sorted by name, so they are not listed in the
118
+ # order they are run.
119
+ _lazy(
120
+ "catalog_author",
121
+ "catalog-author",
122
+ "author one converged public catalog generation into bounded authoring shards",
123
+ ),
124
+ _lazy(
125
+ "catalog_fill",
126
+ "catalog-fill",
127
+ "stage, reconcile, and publish one bounded public catalog successor",
128
+ ),
129
+ _lazy(
130
+ "catalog_harvest",
131
+ "catalog-harvest",
132
+ "stage one keyed public catalog sweep, or just check the key file boundary",
133
+ ),
134
+ _lazy(
135
+ "catalog_publish",
136
+ "catalog-publish",
137
+ "classify one authored generation, publish it, and write its one composite receipt",
138
+ ),
139
+ _lazy(
140
+ "catalog_search",
141
+ "catalog-search",
142
+ "search one exact verified sealed source catalog",
143
+ ),
144
+ _lazy(
145
+ "catalog_update",
146
+ "catalog-update",
147
+ "install one published catalog release after checking its exact fingerprint",
148
+ ),
149
+ _lazy(
150
+ "deploy",
151
+ "deploy",
152
+ "stage one verified Build and activate it as the authenticated Editor",
153
+ ),
154
+ _lazy(
155
+ "deploy_dataset",
156
+ "deploy-dataset",
157
+ "bring the Build a deployment released back here and write its dataset notebook",
158
+ ),
159
+ _lazy(
160
+ "deploy_plan",
161
+ "deploy-plan",
162
+ "plan a live dataset request without deploying it",
163
+ ),
164
+ _lazy(
165
+ "deploy_status",
166
+ "deploy-status",
167
+ "report what Studio says about the run one deployment queued",
168
+ ),
169
+ _lazy("diff", "diff", "compare two builds and say what changed"),
170
+ _lazy(
171
+ "index",
172
+ "index",
173
+ "build one bounded private index from exact-verified local Builds",
174
+ ),
175
+ _lazy(
176
+ "inventory",
177
+ "inventory",
178
+ "list what this build can do: operations, types, joins, engines, and checks",
179
+ ),
180
+ _lazy(
181
+ "list_builds",
182
+ "list",
183
+ "list the builds under a folder, and say whether they were checked",
184
+ ),
185
+ _lazy(
186
+ "login",
187
+ "login",
188
+ "sign in to the Mostly Right cloud and store a device credential",
189
+ ),
190
+ _lazy(
191
+ "peek",
192
+ "peek",
193
+ "open any file or public https address and show its columns, types, and first rows",
194
+ ),
195
+ _lazy(
196
+ "plan_check",
197
+ "plan-check",
198
+ "build this plan into a throwaway folder to see whether it works, then throw it away",
199
+ ),
200
+ _lazy("preflight", "preflight", "check what would stop a build before you start it"),
201
+ _lazy(
202
+ "record",
203
+ "record",
204
+ "seal already-recorded live messages into batches a build can read",
205
+ ),
206
+ _lazy(
207
+ "review_setup",
208
+ "review-setup",
209
+ "create protected review material; this does not approve a Build",
210
+ ),
211
+ _lazy(
212
+ "search",
213
+ "search",
214
+ "search exact published public and private local stores without changing them",
215
+ ),
216
+ _lazy(
217
+ "show",
218
+ "show",
219
+ "show one build: what it answers, what a row is, its columns, checks, and fingerprints",
220
+ ),
221
+ _lazy(
222
+ "whoami",
223
+ "whoami",
224
+ "compatibility alias for auth status",
225
+ ),
226
+ )
227
+
228
+
229
+ def discovered_commands() -> tuple[CommandSpec, ...]:
230
+ """Return the closed descriptor tuple without importing implementation modules."""
231
+
232
+ return _DISCOVERED
233
+
234
+
235
+ def _module_names(search_path: Sequence[str] | None = None) -> tuple[str, ...]:
236
+ """Module names in this package, sorted, excluding private ones."""
237
+
238
+ path = list(search_path) if search_path is not None else list(__path__)
239
+ return tuple(
240
+ sorted(name for _finder, name, _ispkg in pkgutil.iter_modules(path) if name[:1] != "_")
241
+ )
242
+
243
+
244
+ def _collect(modules: Iterable[tuple[str, Any]]) -> tuple[CommandSpec, ...]:
245
+ """Read one ``SPEC`` per module, preserving order and refusing duplicate command names."""
246
+
247
+ specs: list[CommandSpec] = []
248
+ seen: dict[str, str] = {}
249
+ for module_name, module in modules:
250
+ spec = getattr(module, "SPEC", None)
251
+ if spec is None:
252
+ continue
253
+ if spec.name in seen:
254
+ raise RuntimeError(
255
+ f"two command modules declare the command name {spec.name!r}: "
256
+ f"{seen[spec.name]!r} and {module_name!r}"
257
+ )
258
+ seen[spec.name] = module_name
259
+ specs.append(spec)
260
+ return tuple(specs)