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,657 @@
1
+ """Plain-language rendering of the payload mappings the CLI already emits as JSON.
2
+
3
+ The renderer is a pure function over the payload mapping: it never re-reads a written file, never
4
+ touches the environment, and never inspects an exception's cause or traceback. That is what keeps
5
+ ``cli._snapshot_result_receipt``'s claim honest -- the plain rendering and the ``--json`` rendering
6
+ carry the same facts from the same dict, so neither can quietly say more than the other.
7
+
8
+ Nothing here prints. Every function returns a string, so the same facts can be rendered into a
9
+ browser page later without re-deriving them.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Mapping, Sequence
15
+ from typing import Any
16
+
17
+ # Plain names for the payload keys a user or their agent reads. Internal identifiers keep their
18
+ # engineering-precise spelling; only the label shown to a human moves (docs/VOCABULARY.md).
19
+ PLAIN_LABELS: dict[str, str] = {
20
+ # Identity of a build
21
+ # The bare key is mapped as well as the compounds: an unmapped key falls back to the key's own
22
+ # spelling, and that fallback is a label no sweep of the source can see, because it is built at
23
+ # render time rather than written down anywhere.
24
+ "candidate": "Build",
25
+ "candidate_digest": "Build fingerprint",
26
+ "candidate_contract_version": "Build contract version",
27
+ "producer_principal": "Built by",
28
+ "producer_attempt": "Build attempt",
29
+ "table_sha256": "Data fingerprint",
30
+ "manifest_version": "Receipt version",
31
+ "validation_policy_digest": "Checks fingerprint",
32
+ "row_count": "Rows",
33
+ "columns": "Columns",
34
+ # Looking at a file or an address before anything is built
35
+ "source": "Looked at",
36
+ "origin": "Opened from",
37
+ "data_format": "Format",
38
+ "input_sha256": "Bytes fingerprint",
39
+ "content_sha256": "Content fingerprint",
40
+ "schema_digest": "Schema fingerprint",
41
+ "schema": "Column facts",
42
+ "sample": "First rows",
43
+ "fetched_from": "Fetched from",
44
+ "saved_to_disk": "Saved to disk",
45
+ "null_count": "Missing values",
46
+ "distinct_count": "Different values",
47
+ "minimum": "Lowest",
48
+ "maximum": "Highest",
49
+ "grain": "One row per",
50
+ "engine": "Engine",
51
+ # Recipes and approvals
52
+ "recipe_id": "Recipe ID",
53
+ "recipe_version": "Recipe version",
54
+ "recipe_digest": "Recipe fingerprint",
55
+ "predecessor_recipe_digest": "Previous recipe fingerprint",
56
+ "approval_id": "Approval ID",
57
+ "approval_digest": "Approval fingerprint",
58
+ "approval_policy_digest": "Approval policy fingerprint",
59
+ "approved_by": "Approved by",
60
+ "approved_at": "Approved at",
61
+ "execution_digest": "Run-settings fingerprint",
62
+ "execution_mode": "Run mode",
63
+ # Review
64
+ "decision_digest": "Decision fingerprint",
65
+ "review_decision_digest": "Review decision fingerprint",
66
+ "acquisition_bundle_digest": "Acquisition evidence fingerprint",
67
+ "review_dir": "Review folder",
68
+ "review_authority": "Who verified this",
69
+ "review_record": "Review record",
70
+ "signed_report": "Signed report",
71
+ "release_eligible": "Ready to go live",
72
+ "drift_report": "Drift alert",
73
+ # Places
74
+ "workspace": "Workbench folder",
75
+ "run_dir": "Build folder",
76
+ "root": "Folder looked in",
77
+ "output": "Wrote",
78
+ "input_root": "Input folder",
79
+ "member_paths": "Receipt contents",
80
+ # Records and receipts
81
+ "schema_version": "Record version",
82
+ "run_id": "Run ID",
83
+ # One deployed run's own state, as Studio holds it. The key spellings stay Studio's; only
84
+ # these labels move, and `repair_task_id` moves because its own fallback label would print
85
+ # the internal name for a fix-it task at somebody sitting at a terminal.
86
+ "run_status": "Run state",
87
+ # `run_disposition`, not `disposition`: the bare key is already a field of the local search
88
+ # trace, and a label declared here reaches every payload that carries the key. Naming this one
89
+ # for the run keeps a label written for a deployed run out of an unrelated command's output.
90
+ "run_disposition": "Where this stands",
91
+ "run_seconds": "Run took, in seconds",
92
+ "watch_limit_seconds": "Waited at most, in seconds",
93
+ "repair_task_id": "Fix-it task ID",
94
+ "revision": "Revision",
95
+ "state_phase": "Stage",
96
+ "publication": "File receipt",
97
+ "expected_size_bytes": "Size in bytes",
98
+ "expected_content_digest": "Content fingerprint",
99
+ "output_bytes": "Bytes written",
100
+ "output_bytes_sha256": "Written-bytes fingerprint",
101
+ "media_type": "Media type",
102
+ "request_sha256": "Request fingerprint",
103
+ "evidence_authority": "Authoritative record",
104
+ "file_observed": "File seen on disk",
105
+ "linearization_point": "Observed at",
106
+ "namespace_linearization": "Observed at",
107
+ "post_linearization_path_persistence": "After this observation",
108
+ "authority": "Authority",
109
+ "hosted_release": "Live in Studio",
110
+ "successor_repair_closure": "Fix-it cycle closed",
111
+ "url": "Address",
112
+ "dir": "Folder",
113
+ # Question and sources
114
+ "question": "Question",
115
+ "question_id": "Question ID",
116
+ "requirements": "Requirements",
117
+ "requirements_id": "Requirements ID",
118
+ "feasibility": "Feasibility",
119
+ "sources": "Sources",
120
+ "source_ids": "Source IDs",
121
+ "source_proposals": "Source options",
122
+ "required_check_ids": "Required checks",
123
+ "quality": "Quality checks",
124
+ "join": "Join",
125
+ "lineage": "Where it came from",
126
+ "counts": "Counts",
127
+ "manifest_sha256": "Manifest fingerprint",
128
+ "aggregate_sha256": "Aggregate fingerprint",
129
+ "entry_count": "Indexed entries",
130
+ "locator_count": "Private locations",
131
+ "diagnostic_count": "Diagnostics",
132
+ "total_bytes": "Indexed bytes",
133
+ "checkpoint_sha256": "Checkpoint fingerprint",
134
+ "staging_sha256": "Complete staging fingerprint",
135
+ "fill_receipt_sha256": "Fill receipt fingerprint",
136
+ "fill_receipt": "Fill receipt",
137
+ "flagged_queue_sha256": "Flagged-rights queue fingerprint",
138
+ "classification_sha256": "Delta classification fingerprint",
139
+ "classification_checkpoint_sha256": "Classification checkpoint fingerprint",
140
+ "publication_sha256": "Publication receipt fingerprint",
141
+ "provider_count_reconciliation": "Provider count rule",
142
+ "convergence_passes": "Convergence passes",
143
+ "reason_code": "Reason code",
144
+ "recommendation": "Recommendation",
145
+ "representations": "Representations",
146
+ "fallback_reason": "Fallback reason",
147
+ "role": "Role",
148
+ "tasks": "Steps",
149
+ "events": "History",
150
+ }
151
+
152
+ # One plain headline sentence per status the harness puts on stdout. Statuses remain machine
153
+ # contract values; only the sentence shown to a human is translated into plain language.
154
+ STATUS_HEADLINES: dict[str, str] = {
155
+ "ready": "Notebook ready.",
156
+ # Workbench folder states (offline._PHASES)
157
+ "initialized": "Workbench folder created.",
158
+ "planned": "Plan checked and recorded.",
159
+ "building": "A build is in progress.",
160
+ "idle": "No Build is running.",
161
+ "done": "Build finished.",
162
+ "candidate_built": "Build sealed.",
163
+ "review_recorded": "Review decision sealed.",
164
+ "failed": "The last build failed.",
165
+ # Workbench folder commands
166
+ "plan_validated": "Plan checked and recorded.",
167
+ "candidate_inspected": "Build inspected and replayed exactly.",
168
+ "candidate_verified": "Build re-checked and intact.",
169
+ "deployment_approval_required": "Deployment is waiting for human approval.",
170
+ "dataset_activation_queued": "Dataset activation queued.",
171
+ # Both answers about a queued run are answers whatever they say. The headline never claims the
172
+ # run finished: which of the thirteen states it is in is the line below it, every time.
173
+ "run_status_reported": "Here is what Studio says about that run.",
174
+ "run_watch_timed_out": (
175
+ "The wait ran out and the run had not settled. Below is the state it was really in."
176
+ ),
177
+ "hosted_dataset_ready": "The released Build is here and its dataset notebook is ready.",
178
+ "reconciled": "The prior deployment result was recovered.",
179
+ # Cloud device-key lifecycle. Each status says what durable local state or Cloud's exact
180
+ # answer established; none implies that a parent-process environment override changed.
181
+ "logged_out": "The stored device credential was removed.",
182
+ "local_recovery_completed": "The local credential recovery is complete.",
183
+ "recovered": "The credential state was recovered.",
184
+ "recovery_required": "Credential recovery is required before this can continue.",
185
+ "revoked": "The device credential was revoked.",
186
+ "rotated": "A new device credential was stored and the old one was revoked.",
187
+ # Recipes
188
+ "frozen_recipe_validated": "Recipe checked. Nothing was approved or run.",
189
+ "recipe_authored": "Recipe written from your YAML.",
190
+ "recipe_exported": "Recipe written.",
191
+ "recipe_approved": "Recipe approved.",
192
+ # The hosted half of an approval. Two words rather than one, because "the ask was recorded"
193
+ # and "a person settled it" are the two different things somebody needs to be able to tell
194
+ # apart -- and a single word covering both is how a pending approval gets read as a yes.
195
+ "approval_requested": "The approval was asked for. A person still has to grant it.",
196
+ "approval_settled": "A person settled that approval.",
197
+ # The decision record, written into a hosted run's own log. Two words for the same reason the
198
+ # two above are two: writing a cell and reading what is written are different answers, and a
199
+ # single word covering both would let a list read as though something had just been recorded.
200
+ "narrative_cell_appended": "Note written into the run's record.",
201
+ "narrative_listed": "The notes written about this run.",
202
+ "version_built": "Recipe version built.",
203
+ "recipe_version_verified": "Recipe version verified.",
204
+ # Review
205
+ "report_signed": "Report signed.",
206
+ "reported": "Report prepared.",
207
+ "reviewed_candidate": "Review passed. No blocking findings.",
208
+ "fixes_required": "Review found blocking findings. Fixes are required.",
209
+ # Local deterministic checks do not create a review decision.
210
+ "local_checks_completed": "Checks run here. Nothing has been decided.",
211
+ "review_setup_created": "Protected review setup created. This does not approve a Build.",
212
+ # Capturing a live source: what was recorded is now sealed and can be built from.
213
+ "stream_recorded": "Recorded messages sealed.",
214
+ # Looking before authoring
215
+ "peeked": "Here is what is in there.",
216
+ # Asking what would stop a build, and what this build can do at all. Both are answers whatever
217
+ # they contain: "this machine cannot write a file safely" is the thing the command was run to
218
+ # find out, so the headline does not change into an apology when it says so.
219
+ "preflight_completed": "Here is what would stop a build on this machine.",
220
+ "inventory_listed": "Here is what this build can do.",
221
+ "catalog_searched": "Here is the best admitted source in that verified catalog.",
222
+ "catalog_fill_incomplete": (
223
+ "Catalog fill stopped incomplete. Resume from its exact checkpoint."
224
+ ),
225
+ "catalog_published": "Public catalog successor published.",
226
+ # The three explicit provider-scale mutations. Each one is a separate answer with its own
227
+ # receipt, so none of them may read as another: a sweep that stopped is not a generation, and
228
+ # a key file that was accepted is not a sweep.
229
+ "harvest_preflight_accepted": (
230
+ "That key file was accepted. Nothing was fetched and nothing was written."
231
+ ),
232
+ "harvest_complete": "The provider sweep finished. Nothing has been published yet.",
233
+ "harvest_incomplete": (
234
+ "The provider sweep stopped before it finished. The reason below says what stopped it; "
235
+ "run the same command again with --resume to carry on from exactly there."
236
+ ),
237
+ "generation_published": "Public catalog generation published, and read back from disk.",
238
+ "generation_incomplete": (
239
+ "The generation stopped before it was published. What is already written is verified and "
240
+ "kept; run the same command again with --resume to carry on from exactly there."
241
+ ),
242
+ "build_index_published": "Private Build index published.",
243
+ "local_search_complete": "Local search complete.",
244
+ # A public store can be searched under a stated work budget, and a budget that closes before
245
+ # every relevant part of it has been read is not an answer. The headline says so out loud
246
+ # rather than letting "no match" stand in for "we stopped looking".
247
+ "local_search_incomplete": (
248
+ "Local search stopped before it finished. Nothing was ruled out; the reason below says "
249
+ "which budget closed."
250
+ ),
251
+ # Writing one generation of catalog entries out into bounded parts. These two words are the
252
+ # whole answer of that command -- it puts its own receipt's `status` at the top of what it
253
+ # prints -- so without a sentence here a reader was shown the bare word `complete`.
254
+ "complete": "Authoring finished. Every record in that generation was written.",
255
+ "incomplete": (
256
+ "Authoring stopped before it finished. The reason below says what stopped it, and what "
257
+ "was written is listed under it."
258
+ ),
259
+ # The catalogue pipeline job, asked to resolve its deployment's configuration and stop. The
260
+ # word is the whole answer: nothing ran, so the headline has to say so rather than let a
261
+ # reader take a printed receipt for a completed stage.
262
+ "dry-run": "Nothing ran. This is the stage that would have run, and what it would have run as.",
263
+ # Trying a plan before committing to it. The build was real; the folder it went into was not
264
+ # kept, so the headline says what was learned rather than what was made.
265
+ "plan_checked": "That plan builds.",
266
+ # Reading back what you already have
267
+ "build_shown": "Here is what this build holds.",
268
+ "builds_listed": "Here are the builds under that folder.",
269
+ # Comparing two of them. Both outcomes are ordinary answers, so both read as answers: a
270
+ # difference is something to look at, not something that went wrong.
271
+ "builds_identical": "Those two builds hold the same data, in the same shape.",
272
+ "builds_differ": "Those two builds are not the same.",
273
+ # Agent-facing surfaces
274
+ "agent_request_validated": "Agent request checked. No model was called.",
275
+ "source_proposals_validated": "Source options checked. Nothing was fetched.",
276
+ "workflow_described": "Here is the workflow and who decides at each step.",
277
+ "adapters_listed": "Here are the available source adapters.",
278
+ "events_read": "Here is the build history.",
279
+ "fleet_scanned": "Here is the worker fleet.",
280
+ "no_new_run": "No new Recipe run was needed.",
281
+ "refused": "That request was refused.",
282
+ "slices_acquired": "The requested source slices were acquired.",
283
+ "source_acquired": "The source was acquired.",
284
+ # Handoff
285
+ "hosted_candidate_exported": "Build packaged for Studio.",
286
+ "viewer_started": "Viewer running.",
287
+ "notebook_ready": "Table notebook ready.",
288
+ # Signing in to the Mostly Right cloud, and reading back what is stored locally
289
+ "device_authenticated": "Signed in. A device credential is stored on this machine.",
290
+ "authenticated": "This machine is signed in.",
291
+ "not_authenticated": "This machine is not signed in.",
292
+ "listed": "Here are the device credentials Cloud knows about.",
293
+ "not_implemented": "That credential operation is not available in this release.",
294
+ "unsupported": "That transport-token operation is not supported.",
295
+ # A deployment plan is only a request document. These statuses deliberately name the plan,
296
+ # never external state: the command contacts nothing, and Studio remains the authority that
297
+ # decides whether anything runs live.
298
+ "deployment_plan_ready": (
299
+ "Here is the request for running this dataset live. Nothing has been sent."
300
+ ),
301
+ "deployment_plan_refused": (
302
+ "A deployment request could not be prepared. The reasons are below."
303
+ ),
304
+ # The hosted lane. Every one of these headlines is about a run on the backend, and none of
305
+ # them claims anything about this machine: "queued" is what Studio accepted, "terminal" is
306
+ # what Studio's own state machine reached, and "the watch ended" is honest about the case
307
+ # where the connection stopped before the run did.
308
+ "run_queued": "The run is queued. Watch it at the address below.",
309
+ "run_reported": "Here is what Studio says about that run.",
310
+ "run_terminal": "The run has finished. Its final state is below.",
311
+ "watch_ended": "The watch ended before the run did. The run is unaffected.",
312
+ "artifacts_downloaded": "The dataset is here, and its digest matched.",
313
+ # The research session. Three words for opening one, because "a worker holds it", "Studio
314
+ # recorded it and nobody holds it yet" and "it ended" are three different answers to the same
315
+ # command; and three for a probe, because a probe that settled without an answer is neither
316
+ # an answer nor still running, and a single word covering all three is how a failed probe gets
317
+ # read as a result.
318
+ "research_session_ready": "The research session is ready. Its worker is warm.",
319
+ "research_session_opened": (
320
+ "The research session is recorded. Its worker has not taken it yet; the state is below."
321
+ ),
322
+ "research_session_ended": "The research session ended before a worker answered.",
323
+ "research_session_reported": "Here is what Studio says about that research session.",
324
+ "research_session_closed": "The research session is closed. Its worker exits.",
325
+ "research_probe_submitted": "The probe is queued. Its answer is read back separately.",
326
+ "research_probe_answered": "The worker answered the probe. The answer is below.",
327
+ "research_probe_settled": "The probe settled without an answer. The state is below.",
328
+ # The authoring front door. Three words because "a stage stopped where you asked", "the
329
+ # proposal exists and nothing has approved it", and "the approval is open and waiting on a
330
+ # person" are three different answers to the one command, and a single word covering all three
331
+ # is how a proposal nobody has approved gets read as a finished one.
332
+ "authoring_stage_complete": "The authoring stage you asked for is done. What is next is below.",
333
+ "recipe_proposed": "The recipe proposal exists. Nothing has approved it yet.",
334
+ "recipe_proposal_approval_requested": (
335
+ "The recipe proposal is open and waiting for a person to approve it."
336
+ ),
337
+ "recipe_proposal_approved": "A person approved the recipe. Queue its first build below.",
338
+ "recipe_proposal_not_approved": "The approval settled without approving the recipe.",
339
+ "recipe_build_queued": "The approval already started the first build. Follow the run below.",
340
+ # `build` against a proposal whose confirmation already started the run: that run is reported
341
+ # rather than a second one queued, and the word says so.
342
+ "run_already_queued": "This build was already queued by the approval. Its run is below.",
343
+ }
344
+
345
+ RECOMMENDATION_HEADLINES: dict[str, str] = {
346
+ "reuse_build": "A verified local Build can be reused.",
347
+ "use_source": "An admitted public source can be used.",
348
+ "no_match": "No fitting local Build or admitted public source was found.",
349
+ "public_incomplete": (
350
+ "The public source search stopped before it finished, so nothing was ruled out."
351
+ ),
352
+ }
353
+
354
+ # Facts a reader looks for first. Everything not named here follows, sorted, so the rendering is
355
+ # deterministic whatever order the payload dict happens to carry.
356
+ _HEADLINE_KEYS: tuple[str, ...] = (
357
+ # Whether anything is stopping you, and then what to do about it. `mr-data preflight` exists to
358
+ # be read by somebody who is stuck, and the list of everything that was fine is not the answer
359
+ # they came for. These two keys appear in no other payload.
360
+ "blocked",
361
+ "what to do",
362
+ # What is about to be frozen comes before the receipt for freezing it: the point of
363
+ # `mr-data approve` is that the block is read before the outcome lines (29-07). The section
364
+ # names below belong to that block alone and appear in no other payload, so naming them here
365
+ # orders the summary without touching how any other command reads.
366
+ "summary",
367
+ "what this answers",
368
+ "one row is",
369
+ "it covers",
370
+ "the columns",
371
+ "the sources",
372
+ # Directly under the source list, because that is what the sentence is about: what the Recipe
373
+ # declares the result is for, and the permission every source above has to carry for it.
374
+ "what the result may be used for",
375
+ "the cleaning steps",
376
+ "the join",
377
+ "the ordered graph steps",
378
+ "the graph output",
379
+ "the checks it must pass",
380
+ "the refresh bounds",
381
+ "the backfill window",
382
+ "the pinned versions",
383
+ "the recipe",
384
+ "the fingerprint being frozen",
385
+ "what freezing means",
386
+ # What a thing is comes before the detail of it, so one listed change reads in the order a
387
+ # person says it: what changed, which column, from what, to what, by how much. Sorted
388
+ # alphabetically these would arrive as "difference, from, related drift alert, to", which is
389
+ # nobody's sentence.
390
+ "what",
391
+ "column",
392
+ "from",
393
+ "to",
394
+ "difference",
395
+ # What a thing is called comes before anything said about it.
396
+ "name",
397
+ "run_id",
398
+ "workspace",
399
+ "run_dir",
400
+ "review_dir",
401
+ "output",
402
+ "url",
403
+ "dir",
404
+ "root",
405
+ "recipe_id",
406
+ "recipe_version",
407
+ "approval_id",
408
+ "candidate_digest",
409
+ "table_sha256",
410
+ "recipe_digest",
411
+ "approval_digest",
412
+ "decision_digest",
413
+ "review_decision_digest",
414
+ "acquisition_bundle_digest",
415
+ "execution_digest",
416
+ "row_count",
417
+ "columns",
418
+ "grain",
419
+ "manifest_version",
420
+ "engine",
421
+ "release_eligible",
422
+ )
423
+
424
+ # Facts that are the last thing a reader should read, whatever else the payload carries. A key
425
+ # named here renders after every other key, with a blank line before it, so a closing sentence
426
+ # stays closing even when the block above it is a list of checks. Only the top level is affected;
427
+ # nested blocks keep their ordinary order.
428
+ _TAIL_KEYS: tuple[str, ...] = (
429
+ # `review --local` must end by saying that human review is still pending.
430
+ "what happens next",
431
+ # `plan-check` must end by saying which fingerprint it did not report, and why. It reads as a
432
+ # closing line only after the fingerprint it does report.
433
+ "what this does not tell you",
434
+ )
435
+
436
+ # Two levels of indentation, then a count. Deeper structure is a machine record, not a human line.
437
+ _MAX_DEPTH = 2
438
+ _INDENT = " "
439
+ _EMPTY = "(none)"
440
+
441
+
442
+ def render_human(payload: Mapping[str, Any]) -> str:
443
+ """Render one payload mapping as plain labelled lines.
444
+
445
+ The ``status`` key becomes the headline sentence and is not repeated as a line. Every other
446
+ key becomes ``label: value``. Nested mappings become indented blocks up to
447
+ :data:`_MAX_DEPTH`; anything deeper renders as a count that points at ``--json``.
448
+
449
+ ⚠ A PAYLOAD THAT STATES ITS OWN ``headline`` STATES IT, and it is not then repeated as a
450
+ labelled field. The status map is the sentence for a status word; a ``headline`` is a sentence
451
+ about this particular answer, which the acquisition receipt carries in both profiles -- how
452
+ many members were fetched, and from where -- and which the status word cannot know. Printing
453
+ both would open every one of those answers with two headlines, the general one and the true
454
+ one, in that order.
455
+ """
456
+
457
+ status = payload.get("status")
458
+ stated = payload.get("headline")
459
+ if isinstance(stated, str) and stated:
460
+ headline = stated
461
+ elif status is None:
462
+ headline = "Done."
463
+ elif status == "local_search_complete" and isinstance(payload.get("recommendation"), Mapping):
464
+ kind = payload["recommendation"].get("kind")
465
+ headline = RECOMMENDATION_HEADLINES.get(str(kind), STATUS_HEADLINES[str(status)])
466
+ else:
467
+ headline = STATUS_HEADLINES.get(str(status), str(status))
468
+ lines = [_printable(headline)]
469
+ lines.extend(_render_mapping(payload, depth=0, skip=("status", "headline")))
470
+ return "\n".join(lines)
471
+
472
+
473
+ def render_error(
474
+ headline: str,
475
+ *,
476
+ code: str | None = None,
477
+ severity: str | None = None,
478
+ remediation: Sequence[str] = (),
479
+ note: str | None = None,
480
+ ) -> str:
481
+ """Render one failure as a plain block: what happened, what to do, what to quote.
482
+
483
+ ``code`` is what a person quotes in a bug report, so it survives into plain mode verbatim.
484
+ Remediation lines are supplied by the error-remediation map; the default is empty. ``note``
485
+ sits with the code rather than with the advice, because it is about this rendering of the
486
+ failure rather than about the failure.
487
+ """
488
+
489
+ # The headline carries a raise-site message, and a raise-site message regularly carries a name
490
+ # somebody else chose -- a path, a column, a member. One refusal is one line, so a name with a
491
+ # line break or an escape sequence in it is written out rather than acted on, by the same rule
492
+ # every other rendered value goes through.
493
+ lines = [_printable(headline)]
494
+ if remediation:
495
+ lines.append("")
496
+ lines.extend(f"{_INDENT}{_printable(str(item))}" for item in remediation)
497
+ tail = []
498
+ if severity is not None:
499
+ tail.append(f"Severity: {severity}")
500
+ if code is not None:
501
+ tail.append(f"Code: {code}")
502
+ if note is not None:
503
+ tail.append(note)
504
+ if tail:
505
+ lines.append("")
506
+ lines.extend(tail)
507
+ return "\n".join(lines)
508
+
509
+
510
+ def render_lines(mapping: Mapping[str, Any], *, skip: Sequence[str] = ()) -> list[str]:
511
+ """Render one mapping as plain labelled lines, with no headline sentence.
512
+
513
+ This is :func:`render_human` without the status line, for a block of facts that is part of a
514
+ larger payload rather than the whole of one. A caller that has its own structured record and
515
+ wants the terminal rendering of it uses this, so the block a person reads and the record a
516
+ machine reads never become two truths.
517
+ """
518
+
519
+ return _render_mapping(mapping, depth=0, skip=skip)
520
+
521
+
522
+ def plain_label(key: str) -> str:
523
+ """Plain label for one payload key; an unmapped key still renders, never crashes."""
524
+
525
+ mapped = PLAIN_LABELS.get(key)
526
+ if mapped is not None:
527
+ return mapped
528
+ return key.replace("_", " ").capitalize()
529
+
530
+
531
+ def _render_mapping(
532
+ mapping: Mapping[str, Any],
533
+ *,
534
+ depth: int,
535
+ skip: Sequence[str] = (),
536
+ ) -> list[str]:
537
+ indent = _INDENT * depth
538
+ lines: list[str] = []
539
+ for key in _ordered_keys(mapping, skip=skip):
540
+ value = mapping[key]
541
+ # Lexical mode was explicitly selected, so no fallback happened. The token remains in the
542
+ # JSON receipt for compatibility but is not a human-facing reason to explain.
543
+ if key == "fallback_reason" and value == "NEURAL_NOT_REQUESTED":
544
+ continue
545
+ label = plain_label(str(key))
546
+ if depth == 0 and key in _TAIL_KEYS and lines:
547
+ lines.append("")
548
+ if isinstance(value, Mapping):
549
+ if depth >= _MAX_DEPTH:
550
+ lines.append(f"{indent}{label}: {_summary(len(value))}")
551
+ continue
552
+ lines.append(f"{indent}{label}:")
553
+ if not value:
554
+ lines.append(f"{indent}{_INDENT}{_EMPTY}")
555
+ continue
556
+ lines.extend(_render_mapping(value, depth=depth + 1))
557
+ continue
558
+ if _is_block(value):
559
+ # A value written over several lines is a block a person reads, not a field. Putting
560
+ # it after its own label keeps every line of it flush with the others -- and every
561
+ # line of it goes through `_printable`, so the newlines this branch honours are the
562
+ # only control characters that survive into the rendering.
563
+ lines.append(f"{indent}{label}:")
564
+ lines.extend(f"{indent}{_INDENT}{_printable(part)}" for part in str(value).split("\n"))
565
+ continue
566
+ if _is_sequence(value):
567
+ items = list(value)
568
+ if not items:
569
+ lines.append(f"{indent}{label}: {_EMPTY}")
570
+ continue
571
+ if all(_is_scalar(item) for item in items):
572
+ joined = ", ".join(_scalar(item) for item in items)
573
+ lines.append(f"{indent}{label}: {joined}")
574
+ continue
575
+ lines.append(f"{indent}{label}: {_summary(len(items))}")
576
+ continue
577
+ lines.append(f"{indent}{label}: {_scalar(value)}")
578
+ return lines
579
+
580
+
581
+ def _ordered_keys(mapping: Mapping[str, Any], *, skip: Sequence[str] = ()) -> list[Any]:
582
+ present = [key for key in mapping if key not in skip]
583
+ tail = [key for key in _TAIL_KEYS if key in present]
584
+ headline = [key for key in _HEADLINE_KEYS if key in present and key not in tail]
585
+ remainder = sorted(
586
+ (key for key in present if key not in headline and key not in tail),
587
+ key=lambda key: (_is_container(mapping[key]), str(key)),
588
+ )
589
+ return [*headline, *remainder, *tail]
590
+
591
+
592
+ def _summary(count: int) -> str:
593
+ noun = "item" if count == 1 else "items"
594
+ return f"<{count} {noun} - use --json for the full record>"
595
+
596
+
597
+ def _is_scalar(value: Any) -> bool:
598
+ return value is None or isinstance(value, str | int | float | bool)
599
+
600
+
601
+ def _is_sequence(value: Any) -> bool:
602
+ return isinstance(value, list | tuple)
603
+
604
+
605
+ def _is_block(value: Any) -> bool:
606
+ """Whether this value is text written over more than one line."""
607
+
608
+ return isinstance(value, str) and "\n" in value
609
+
610
+
611
+ def _is_container(value: Any) -> bool:
612
+ return isinstance(value, Mapping) or _is_sequence(value) or _is_block(value)
613
+
614
+
615
+ def _scalar(value: Any) -> str:
616
+ if value is None:
617
+ return _EMPTY
618
+ if isinstance(value, bool):
619
+ return "yes" if value else "no"
620
+ return _printable(str(value))
621
+
622
+
623
+ def _printable(text: str) -> str:
624
+ """One line of text with every character that is not one written as an escape.
625
+
626
+ A payload value is regularly a cell out of a file nobody has cleaned -- that is what `mr-data
627
+ peek` is for -- and the plain rendering is what a person, and per SKILL.md a driving agent,
628
+ reads and branches on. Text goes to a terminal, and a terminal acts on some of what it is
629
+ given: a newline in a cell puts attacker-chosen text at column 0, where `[ok] ...` and
630
+ `All checks passed: yes` live, and an escape sequence in a cell reaches the terminal itself.
631
+ Neither is a thing this renderer may pass on.
632
+
633
+ The rule is the Reader's own, one level down. A column *name* carrying a control character is
634
+ refused outright (`PARSE_COLUMN_NAME`); a value may not be refused, because refusing messy
635
+ values would leave peek unable to do the only thing it exists for -- so it is shown instead,
636
+ exactly, as the escape that spells it. ``str.isprintable`` is the test, so the C0 and C1
637
+ control characters, the delete character, and the line and paragraph separators are all
638
+ covered by one rule rather than by a list that has to be kept up to date.
639
+
640
+ A multi-line string that is a block of the product's own prose never reaches here: it is
641
+ rendered as an indented block, where every line is already flush with the others and none of
642
+ them can start at column 0. Each line of that block is still put through this function.
643
+ """
644
+
645
+ if text.isprintable():
646
+ return text
647
+ return "".join(part if part.isprintable() else _escaped(part) for part in text)
648
+
649
+
650
+ def _escaped(character: str) -> str:
651
+ """One character that cannot be printed, written the way Python writes it in a string.
652
+
653
+ ``repr`` is the spelling, so a newline reads as ``\\n`` and an escape as ``\\x1b`` -- the form
654
+ a person recognises and can paste back into a search.
655
+ """
656
+
657
+ return repr(character)[1:-1]