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,744 @@
1
+ """Remote-truthful device authentication status and metadata commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ from collections.abc import Mapping
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from mostlyright.data_harness.key_seam import LEGACY_API_KEY_ENV
12
+ from mostlyright.data_harness.ux.cloud_auth import CloudAuthClient, CloudAuthError
13
+ from mostlyright.data_harness.ux.commands import CommandSpec
14
+ from mostlyright.data_harness.ux.credential_store import (
15
+ CredentialEnvelope,
16
+ CredentialStoreError,
17
+ credential_lock,
18
+ )
19
+ from mostlyright.data_harness.ux.credentials import (
20
+ LoginError,
21
+ credential_transaction_store,
22
+ credentials_path,
23
+ read_credentials,
24
+ remove_credential_manifest_under_held_lock,
25
+ resolve_cloud_credentials,
26
+ )
27
+
28
+ _STATUS_SCHEMA = "mr-data-auth-status.v1"
29
+ _DEVICES_SCHEMA = "mr-data-auth-devices.v1"
30
+ _TOKEN_SCHEMA = "mr-data-auth-token-boundary.v1"
31
+ _LOGOUT_SCHEMA = "mr-data-auth-logout.v1"
32
+ _RECOVERY_SCHEMA = "mr-data-auth-recovery.v1"
33
+ _ROTATE_SCHEMA = "mr-data-auth-rotate.v1"
34
+ _DEVICE_REVOKE_SCHEMA = "mr-data-auth-device-revoke.v1"
35
+ _REFUSAL_CODES = frozenset({"CLOUD_AUTH_FORBIDDEN", "CLOUD_AUTH_NOT_FOUND"})
36
+
37
+ DESCRIPTION = (
38
+ "Inspect the effective Mostly Right device authority through Cloud. Status validates only the "
39
+ "effective credential and shows stored metadata separately. Device inventory is metadata-only; "
40
+ "Studio access tokens are ephemeral transport credentials, not local credentials."
41
+ )
42
+
43
+
44
+ def add_arguments(parser: argparse.ArgumentParser) -> None:
45
+ """Declare the whole offline-discoverable auth hierarchy."""
46
+
47
+ parser.description = DESCRIPTION
48
+ subcommands = parser.add_subparsers(
49
+ dest="auth_command", required=True, help="device-authority operation"
50
+ )
51
+ nested_json = argparse.ArgumentParser(add_help=False)
52
+ nested_json.add_argument("--json", action="store_true", help=argparse.SUPPRESS)
53
+
54
+ status = subcommands.add_parser(
55
+ "status", parents=[nested_json], help="validate the effective device credential"
56
+ )
57
+ status.add_argument(
58
+ "--credential-store",
59
+ choices=("secure-file",),
60
+ help="explicitly use the secure-file fallback for first credential migration",
61
+ )
62
+ status.set_defaults(auth_operation="status")
63
+
64
+ devices = subcommands.add_parser(
65
+ "devices", parents=[nested_json], help="inspect or manage Cloud device keys"
66
+ )
67
+ device_commands = devices.add_subparsers(
68
+ dest="devices_command", required=True, help="device-key operation"
69
+ )
70
+ device_commands.add_parser(
71
+ "list", parents=[nested_json], help="list Cloud device-key metadata in Cloud order"
72
+ ).set_defaults(auth_operation="devices_list")
73
+ revoke = device_commands.add_parser(
74
+ "revoke",
75
+ parents=[nested_json],
76
+ help="revoke one Cloud device key with remote-first local cleanup",
77
+ )
78
+ revoke.add_argument("key_id", metavar="KEY_ID", help="Cloud device-key identifier")
79
+ revoke.set_defaults(auth_operation="devices_revoke")
80
+
81
+ tokens = subcommands.add_parser(
82
+ "tokens", parents=[nested_json], help="describe the Studio transport-token boundary"
83
+ )
84
+ token_commands = tokens.add_subparsers(
85
+ dest="tokens_command", required=True, help="transport-token operation"
86
+ )
87
+ token_commands.add_parser(
88
+ "list", parents=[nested_json], help="explain why ephemeral tokens cannot be listed"
89
+ ).set_defaults(auth_operation="tokens_list")
90
+ token_commands.add_parser(
91
+ "revoke", parents=[nested_json], help="explain why ephemeral tokens cannot be revoked"
92
+ ).set_defaults(auth_operation="tokens_revoke")
93
+
94
+ login = subcommands.add_parser(
95
+ "login", parents=[nested_json], help="sign in with a device code"
96
+ )
97
+ login.add_argument("--device-name", help="what to call this device in Cloud")
98
+ login.add_argument(
99
+ "--credential-store", choices=("secure-file",), help="explicit secure-file store"
100
+ )
101
+ login.set_defaults(auth_operation="login")
102
+ rotate = subcommands.add_parser(
103
+ "rotate", parents=[nested_json], help="replace a device key safely"
104
+ )
105
+ rotate.add_argument("--device-name", help="what to call the replacement device")
106
+ rotate.add_argument(
107
+ "--credential-store", choices=("secure-file",), help="explicit secure-file store"
108
+ )
109
+ rotate.set_defaults(auth_operation="rotate")
110
+ logout = subcommands.add_parser(
111
+ "logout",
112
+ parents=[nested_json],
113
+ help="remotely revoke then remove stored authority",
114
+ )
115
+ logout.add_argument(
116
+ "--local-only",
117
+ action="store_true",
118
+ help="remove local state without claiming remote revocation",
119
+ )
120
+ logout.add_argument(
121
+ "--credential-store", choices=("secure-file",), help="explicit secure-file store"
122
+ )
123
+ logout.set_defaults(auth_operation="logout")
124
+ recover = subcommands.add_parser(
125
+ "recover",
126
+ parents=[nested_json],
127
+ help="resume or explicitly clear interrupted credential state",
128
+ )
129
+ recover.add_argument(
130
+ "--local-only",
131
+ action="store_true",
132
+ help="clear only a stale login lease; remote mint remains unproved",
133
+ )
134
+ recover.add_argument(
135
+ "--credential-store", choices=("secure-file",), help="explicit secure-file store"
136
+ )
137
+ recover.set_defaults(auth_operation="recover")
138
+
139
+
140
+ def run(args: argparse.Namespace) -> dict[str, Any]:
141
+ operation = args.auth_operation
142
+ if operation == "status":
143
+ return status_payload(credential_store=args.credential_store)
144
+ if operation == "devices_list":
145
+ return devices_list_payload()
146
+ if operation == "devices_revoke":
147
+ return device_revoke_payload(args.key_id)
148
+ if operation == "login":
149
+ return login_payload(device_name=args.device_name, credential_store=args.credential_store)
150
+ if operation == "rotate":
151
+ return rotate_payload(device_name=args.device_name, credential_store=args.credential_store)
152
+ if operation == "logout":
153
+ return logout_payload(local_only=args.local_only, credential_store=args.credential_store)
154
+ if operation == "recover":
155
+ return recover_payload(local_only=args.local_only, credential_store=args.credential_store)
156
+ if operation == "tokens_list":
157
+ return tokens_payload("list")
158
+ if operation == "tokens_revoke":
159
+ return tokens_payload("revoke")
160
+ return _reserved_lifecycle_payload(operation.removeprefix("reserved_"))
161
+
162
+
163
+ def exit_code(payload: Mapping[str, Any]) -> int:
164
+ """Derive the one command exit from the one receipt payload."""
165
+
166
+ status = payload.get("status")
167
+ if status in {
168
+ "authenticated",
169
+ "not_authenticated",
170
+ "listed",
171
+ "logged_out",
172
+ "local_recovery_completed",
173
+ "recovered",
174
+ "device_authenticated",
175
+ "rotated",
176
+ "revoked",
177
+ }:
178
+ return 0
179
+ if status in {"unsupported", "not_implemented", "refused"}:
180
+ return 2
181
+ return 1
182
+
183
+
184
+ def status_payload(*, credential_store: str | None = None) -> dict[str, Any]:
185
+ """Project remote identity for exactly one effective source, never two."""
186
+
187
+ try:
188
+ effective = resolve_cloud_credentials(credential_store=credential_store)
189
+ except LoginError as error:
190
+ if error.code == "LOGIN_REQUIRED":
191
+ return {
192
+ "schema_version": _STATUS_SCHEMA,
193
+ "status": "not_authenticated",
194
+ "effective_source": None,
195
+ "stored_state": _stored_state(credential_store),
196
+ "remote_validation": "not_attempted",
197
+ }
198
+ return _storage_error_payload(error, source=None)
199
+
200
+ stored_state = _stored_state(credential_store)
201
+ try:
202
+ current = CloudAuthClient(effective.cloud_url, effective.raw_key).current()
203
+ except CloudAuthError as error:
204
+ status = "refused" if error.code in _REFUSAL_CODES else "error"
205
+ payload: dict[str, Any] = {
206
+ "schema_version": _STATUS_SCHEMA,
207
+ "status": status,
208
+ "effective_source": effective.source,
209
+ "stored_state": stored_state,
210
+ "remote_validation": "refused" if status == "refused" else "failed",
211
+ "error_code": error.code,
212
+ }
213
+ if error.retry_after_seconds is not None:
214
+ payload["retry_after_seconds"] = error.retry_after_seconds
215
+ return payload
216
+
217
+ payload = {
218
+ "schema_version": _STATUS_SCHEMA,
219
+ "status": "authenticated",
220
+ "effective_source": effective.source,
221
+ "stored_state": stored_state,
222
+ "remote_validation": "validated",
223
+ **current.to_receipt(),
224
+ }
225
+ if effective.source == "environment":
226
+ payload["environment_notice"] = (
227
+ "MOSTLYRIGHT_API_KEY is effective for this process; mr-data cannot unset a "
228
+ "parent-process environment credential."
229
+ )
230
+ return payload
231
+
232
+
233
+ def devices_list_payload() -> dict[str, Any]:
234
+ """List only Cloud's allowlisted device metadata, in its supplied order."""
235
+
236
+ try:
237
+ effective = resolve_cloud_credentials()
238
+ except LoginError as error:
239
+ return _devices_login_error(error)
240
+ try:
241
+ devices = CloudAuthClient(effective.cloud_url, effective.raw_key).list_devices()
242
+ except CloudAuthError as error:
243
+ status = "refused" if error.code in _REFUSAL_CODES else "error"
244
+ payload: dict[str, Any] = {
245
+ "schema_version": _DEVICES_SCHEMA,
246
+ "status": status,
247
+ "effective_source": effective.source,
248
+ "error_code": error.code,
249
+ }
250
+ if error.retry_after_seconds is not None:
251
+ payload["retry_after_seconds"] = error.retry_after_seconds
252
+ return payload
253
+ return {
254
+ "schema_version": _DEVICES_SCHEMA,
255
+ "status": "listed",
256
+ "effective_source": effective.source,
257
+ "devices": [device.to_receipt() for device in devices],
258
+ }
259
+
260
+
261
+ def reserved_device_revoke_payload(key_id: str) -> dict[str, Any]:
262
+ """Parse the future mutation coordinate without performing a local mutation early."""
263
+
264
+ del key_id
265
+ return {
266
+ "schema_version": _DEVICES_SCHEMA,
267
+ "status": "not_implemented",
268
+ "detail": "device revoke is reserved for the remote-first lifecycle command",
269
+ }
270
+
271
+
272
+ def _store_for_mutation(
273
+ *, config_dir: Path | None, credential_store: str | None
274
+ ) -> tuple[Any, Path] | None:
275
+ return credential_transaction_store(config_dir=config_dir, credential_store=credential_store)
276
+
277
+
278
+ def _mutation_error(
279
+ schema: str,
280
+ error: Exception | None = None,
281
+ *,
282
+ status: str = "error",
283
+ remote_revocation: str = "unproved",
284
+ local_state: str = "retained",
285
+ ) -> dict[str, Any]:
286
+ payload: dict[str, Any] = {
287
+ "schema_version": schema,
288
+ "status": status,
289
+ "remote_revocation": remote_revocation,
290
+ "local_state": local_state,
291
+ }
292
+ if isinstance(error, (CloudAuthError, LoginError, CredentialStoreError)):
293
+ payload["error_code"] = error.code
294
+ return _with_environment_authority(payload)
295
+
296
+
297
+ def _client(
298
+ cloud_url: str,
299
+ raw_key: str,
300
+ cloud_client_factory: Any | None,
301
+ ) -> Any:
302
+ factory = CloudAuthClient if cloud_client_factory is None else cloud_client_factory
303
+ return factory(cloud_url, raw_key)
304
+
305
+
306
+ def _remove_local_under_lock(
307
+ store: Any,
308
+ path: Path,
309
+ envelope: CredentialEnvelope,
310
+ *,
311
+ fail_after_protected_delete: bool = False,
312
+ ) -> str:
313
+ """Remove the manifest before protected state, never inventing durable remote proof."""
314
+
315
+ remove_credential_manifest_under_held_lock(path)
316
+ # This fault seam models the only recoverable cleanup crash: the protected
317
+ # logout_pending envelope remains selected by the native default or an
318
+ # explicit secure-file selector and can replay its exact Cloud DELETE.
319
+ if fail_after_protected_delete:
320
+ return "pending"
321
+ store.delete_under_held_lock(
322
+ expected_generation=envelope.generation, expected_states=frozenset({envelope.state})
323
+ )
324
+ return "removed"
325
+
326
+
327
+ def _with_environment_authority(payload: dict[str, Any]) -> dict[str, Any]:
328
+ if LEGACY_API_KEY_ENV in os.environ:
329
+ payload["environment_authority"] = "remains_effective"
330
+ return payload
331
+
332
+
333
+ def logout_payload(
334
+ *,
335
+ local_only: bool = False,
336
+ config_dir: Path | None = None,
337
+ credential_store: str | None = None,
338
+ cloud_client_factory: Any | None = None,
339
+ fail_after_pending: bool = False,
340
+ fail_after_protected_delete: bool = False,
341
+ ) -> dict[str, Any]:
342
+ """Remotely prove self-revocation before normal local credential deletion.
343
+
344
+ ``fail_after_pending`` is a deterministic fault-injection seam used only by the transaction
345
+ tests: it models a crash after the remote commit and durable pending transition.
346
+ """
347
+
348
+ try:
349
+ opened = _store_for_mutation(config_dir=config_dir, credential_store=credential_store)
350
+ except LoginError as error:
351
+ if local_only:
352
+ return _local_only_unreadable(config_dir=config_dir, error=error)
353
+ return _mutation_error(_LOGOUT_SCHEMA, error)
354
+ if opened is None:
355
+ return _mutation_error(_LOGOUT_SCHEMA, status="refused", local_state="not_configured")
356
+ store, path = opened
357
+
358
+ if local_only:
359
+ try:
360
+ with credential_lock(store.config_dir):
361
+ envelope = store.load_under_held_lock()
362
+ if envelope is None:
363
+ remove_credential_manifest_under_held_lock(path)
364
+ else:
365
+ _remove_local_under_lock(store, path, envelope)
366
+ except CredentialStoreError:
367
+ # Explicit local-only recovery is the one path permitted to remove unreadable
368
+ # protected bytes. It makes no claim that Cloud was reached or that revocation won.
369
+ try:
370
+ with credential_lock(store.config_dir):
371
+ store.backend.delete()
372
+ remove_credential_manifest_under_held_lock(path)
373
+ except (LoginError, CredentialStoreError) as error:
374
+ return _mutation_error(_LOGOUT_SCHEMA, error)
375
+ except LoginError as error:
376
+ return _mutation_error(_LOGOUT_SCHEMA, error)
377
+ return _with_environment_authority(
378
+ {
379
+ "schema_version": _LOGOUT_SCHEMA,
380
+ "status": "local_recovery_completed",
381
+ "remote_revocation": "unproved",
382
+ "local_state": "removed",
383
+ }
384
+ )
385
+
386
+ try:
387
+ effective = resolve_cloud_credentials(
388
+ config_dir=config_dir, credential_store=credential_store
389
+ )
390
+ except LoginError as error:
391
+ return _mutation_error(_LOGOUT_SCHEMA, error)
392
+ if effective.source != "stored_device":
393
+ return _mutation_error(_LOGOUT_SCHEMA, status="refused")
394
+
395
+ try:
396
+ with credential_lock(store.config_dir):
397
+ envelope = store.load_under_held_lock()
398
+ if envelope is None or envelope.state != "active" or envelope.active is None:
399
+ return _mutation_error(_LOGOUT_SCHEMA, status="refused")
400
+ active = envelope.active
401
+ client = _client(active.cloud_url, active.raw_key, cloud_client_factory)
402
+ current = client.current()
403
+ if current.device.key_id != active.key_id:
404
+ return _mutation_error(_LOGOUT_SCHEMA, status="error")
405
+ # Persist the replay coordinate before the irrevocable remote call. If the
406
+ # process dies after Cloud accepts DELETE, recovery owns this exact credential
407
+ # and may ask Cloud only for the documented same-secret replay proof.
408
+ pending = CredentialEnvelope.pending_logout(
409
+ generation=envelope.generation + 1, active=active
410
+ )
411
+ store.replace_under_held_lock(
412
+ pending,
413
+ expected_generation=envelope.generation,
414
+ expected_states=frozenset({"active"}),
415
+ )
416
+ try:
417
+ client.revoke(active.key_id, exact_secret_same_key_replay=True)
418
+ except CloudAuthError as error:
419
+ return _mutation_error(
420
+ _LOGOUT_SCHEMA,
421
+ error,
422
+ status="recovery_required",
423
+ remote_revocation="unproved",
424
+ local_state="pending",
425
+ )
426
+ if fail_after_pending:
427
+ return _mutation_error(
428
+ _LOGOUT_SCHEMA,
429
+ status="recovery_required",
430
+ remote_revocation="proved",
431
+ local_state="pending",
432
+ )
433
+ try:
434
+ state = _remove_local_under_lock(
435
+ store,
436
+ path,
437
+ pending,
438
+ fail_after_protected_delete=fail_after_protected_delete,
439
+ )
440
+ if state == "pending":
441
+ return _mutation_error(
442
+ _LOGOUT_SCHEMA,
443
+ status="recovery_required",
444
+ remote_revocation="proved",
445
+ local_state="pending",
446
+ )
447
+ except (LoginError, CredentialStoreError) as error:
448
+ return _mutation_error(
449
+ _LOGOUT_SCHEMA,
450
+ error,
451
+ status="recovery_required",
452
+ remote_revocation="proved",
453
+ local_state="pending",
454
+ )
455
+ except (CloudAuthError, LoginError, CredentialStoreError) as error:
456
+ return _mutation_error(_LOGOUT_SCHEMA, error)
457
+ return _with_environment_authority(
458
+ {
459
+ "schema_version": _LOGOUT_SCHEMA,
460
+ "status": "logged_out",
461
+ "remote_revocation": "proved",
462
+ "local_state": "removed",
463
+ }
464
+ )
465
+
466
+
467
+ def _local_only_unreadable(*, config_dir: Path | None, error: LoginError) -> dict[str, Any]:
468
+ """Remove an unreadable control file only when the caller made local-only intent explicit."""
469
+
470
+ path = credentials_path(config_dir)
471
+ try:
472
+ if path.exists() or path.is_symlink():
473
+ path.unlink()
474
+ except OSError:
475
+ return _mutation_error(_LOGOUT_SCHEMA, error)
476
+ return _with_environment_authority(
477
+ {
478
+ "schema_version": _LOGOUT_SCHEMA,
479
+ "status": "local_recovery_completed",
480
+ "remote_revocation": "unproved",
481
+ "local_state": "removed",
482
+ }
483
+ )
484
+
485
+
486
+ def recover_payload(
487
+ *,
488
+ local_only: bool = False,
489
+ config_dir: Path | None = None,
490
+ credential_store: str | None = None,
491
+ cloud_client_factory: Any | None = None,
492
+ ) -> dict[str, Any]:
493
+ """Resume one closed durable transaction state without inventing remote proof."""
494
+
495
+ try:
496
+ opened = _store_for_mutation(config_dir=config_dir, credential_store=credential_store)
497
+ except LoginError as error:
498
+ return _mutation_error(_RECOVERY_SCHEMA, error)
499
+ if opened is None:
500
+ return _mutation_error(_RECOVERY_SCHEMA, status="refused", local_state="not_configured")
501
+ store, path = opened
502
+ try:
503
+ with credential_lock(store.config_dir):
504
+ envelope = store.load_under_held_lock()
505
+ if envelope is None:
506
+ return _mutation_error(
507
+ _RECOVERY_SCHEMA,
508
+ status="refused",
509
+ remote_revocation="unproved",
510
+ local_state="retained",
511
+ )
512
+ if envelope.state == "logout_pending":
513
+ assert envelope.active is not None
514
+ client = _client(
515
+ envelope.active.cloud_url,
516
+ envelope.active.raw_key,
517
+ cloud_client_factory,
518
+ )
519
+ client.revoke(envelope.active.key_id, exact_secret_same_key_replay=True)
520
+ _remove_local_under_lock(store, path, envelope)
521
+ return _with_environment_authority(
522
+ {
523
+ "schema_version": _RECOVERY_SCHEMA,
524
+ "status": "recovered",
525
+ "remote_revocation": "proved",
526
+ "local_state": "removed",
527
+ }
528
+ )
529
+ if envelope.state == "login_pending":
530
+ if not local_only:
531
+ return _mutation_error(
532
+ _RECOVERY_SCHEMA, status="refused", local_state="pending"
533
+ )
534
+ store.recover_login_lease_local_only()
535
+ return _with_environment_authority(
536
+ {
537
+ "schema_version": _RECOVERY_SCHEMA,
538
+ "status": "local_recovery_completed",
539
+ "remote_revocation": "unproved",
540
+ "local_state": "recovered",
541
+ "mint_outcome": "unproved",
542
+ }
543
+ )
544
+ if envelope.state == "revoke_pending":
545
+ assert envelope.active is not None and envelope.prior is not None
546
+ client = _client(
547
+ envelope.prior.cloud_url, envelope.prior.raw_key, cloud_client_factory
548
+ )
549
+ client.revoke(envelope.prior.key_id, exact_secret_same_key_replay=True)
550
+ cleanup = CredentialEnvelope.pending_cleanup(
551
+ generation=envelope.generation + 1,
552
+ active=envelope.active,
553
+ prior=envelope.prior,
554
+ )
555
+ store.replace_under_held_lock(
556
+ cleanup,
557
+ expected_generation=envelope.generation,
558
+ expected_states=frozenset({"revoke_pending"}),
559
+ )
560
+ envelope = cleanup
561
+ if envelope.state == "cleanup_pending":
562
+ assert envelope.active is not None
563
+ active = CredentialEnvelope.active_envelope(
564
+ generation=envelope.generation + 1, active=envelope.active
565
+ )
566
+ store.replace_under_held_lock(
567
+ active,
568
+ expected_generation=envelope.generation,
569
+ expected_states=frozenset({"cleanup_pending"}),
570
+ )
571
+ return _with_environment_authority(
572
+ {
573
+ "schema_version": _RECOVERY_SCHEMA,
574
+ "status": "recovered",
575
+ "remote_revocation": "proved",
576
+ "local_state": "active",
577
+ }
578
+ )
579
+ return _mutation_error(_RECOVERY_SCHEMA, status="refused", local_state="active")
580
+ except (CloudAuthError, LoginError, CredentialStoreError) as error:
581
+ return _mutation_error(_RECOVERY_SCHEMA, error, local_state="pending")
582
+
583
+
584
+ def device_revoke_payload(
585
+ key_id: str,
586
+ *,
587
+ config_dir: Path | None = None,
588
+ credential_store: str | None = None,
589
+ cloud_client_factory: Any | None = None,
590
+ ) -> dict[str, Any]:
591
+ """Revoke a sibling remotely, or use the logout transaction for the stored current key."""
592
+
593
+ try:
594
+ effective = resolve_cloud_credentials(
595
+ config_dir=config_dir, credential_store=credential_store
596
+ )
597
+ client = _client(effective.cloud_url, effective.raw_key, cloud_client_factory)
598
+ current = client.current()
599
+ except (CloudAuthError, LoginError) as error:
600
+ return _mutation_error(_DEVICE_REVOKE_SCHEMA, error)
601
+ if current.device.key_id == key_id and effective.source == "stored_device":
602
+ result = logout_payload(
603
+ config_dir=config_dir,
604
+ credential_store=credential_store,
605
+ cloud_client_factory=cloud_client_factory,
606
+ )
607
+ return {
608
+ "schema_version": _DEVICE_REVOKE_SCHEMA,
609
+ "status": "revoked" if result["status"] == "logged_out" else result["status"],
610
+ "remote_revocation": result["remote_revocation"],
611
+ "local_state": result["local_state"],
612
+ }
613
+ try:
614
+ client.revoke(key_id)
615
+ except CloudAuthError as error:
616
+ return _mutation_error(_DEVICE_REVOKE_SCHEMA, error)
617
+ local_state = (
618
+ "environment_remains_effective" if effective.source == "environment" else "retained"
619
+ )
620
+ if effective.source == "environment":
621
+ local_state = _cleanup_environment_revoked_stored_target(
622
+ key_id, config_dir=config_dir, credential_store=credential_store
623
+ )
624
+ payload: dict[str, Any] = {
625
+ "schema_version": _DEVICE_REVOKE_SCHEMA,
626
+ "status": "revoked",
627
+ "remote_revocation": "proved",
628
+ "local_state": local_state,
629
+ }
630
+ if effective.source == "environment":
631
+ payload["environment_authority"] = "remains_effective"
632
+ return payload
633
+
634
+
635
+ def _cleanup_environment_revoked_stored_target(
636
+ key_id: str,
637
+ *,
638
+ config_dir: Path | None,
639
+ credential_store: str | None,
640
+ ) -> str:
641
+ """Remove only a stored key whose already-proved sibling DELETE targeted it."""
642
+
643
+ try:
644
+ opened = _store_for_mutation(config_dir=config_dir, credential_store=credential_store)
645
+ if opened is None:
646
+ return "retained"
647
+ store, path = opened
648
+ with credential_lock(store.config_dir):
649
+ envelope = store.load_under_held_lock()
650
+ if envelope is None or envelope.state != "active" or envelope.active is None:
651
+ return "retained"
652
+ if envelope.active.key_id != key_id:
653
+ return "retained"
654
+ pending = CredentialEnvelope.pending_logout(
655
+ generation=envelope.generation + 1, active=envelope.active
656
+ )
657
+ store.replace_under_held_lock(
658
+ pending,
659
+ expected_generation=envelope.generation,
660
+ expected_states=frozenset({"active"}),
661
+ )
662
+ return _remove_local_under_lock(store, path, pending)
663
+ except (LoginError, CredentialStoreError):
664
+ return "pending"
665
+
666
+
667
+ def login_payload(*, device_name: str | None, credential_store: str | None) -> dict[str, Any]:
668
+ from mostlyright.data_harness.ux.login import run_login
669
+
670
+ return run_login(device_name=device_name, credential_store=credential_store)
671
+
672
+
673
+ def rotate_payload(*, device_name: str | None, credential_store: str | None) -> dict[str, Any]:
674
+ from mostlyright.data_harness.ux.login import run_login
675
+
676
+ return run_login(device_name=device_name, force=True, credential_store=credential_store)
677
+
678
+
679
+ def tokens_payload(operation: str) -> dict[str, Any]:
680
+ """Tell the truth about a capability Studio does not expose."""
681
+
682
+ if operation not in {"list", "revoke"}:
683
+ return {
684
+ "schema_version": _TOKEN_SCHEMA,
685
+ "status": "error",
686
+ "error_code": "AUTH_TOKEN_OPERATION_INVALID",
687
+ }
688
+ return {
689
+ "schema_version": _TOKEN_SCHEMA,
690
+ "status": "unsupported",
691
+ "operation": operation,
692
+ "detail": (
693
+ "Studio access tokens are minted ephemerally for transport, not locally stored, "
694
+ "not listable, and not individually revocable. Rotate, revoke, or log out the "
695
+ "device key instead."
696
+ ),
697
+ }
698
+
699
+
700
+ def _stored_state(credential_store: str | None) -> dict[str, Any]:
701
+ try:
702
+ stored = read_credentials(credential_store=credential_store)
703
+ except LoginError as error:
704
+ return {"status": "unavailable", "error_code": error.code}
705
+ if stored is None:
706
+ return {"status": "not_configured"}
707
+ return {"status": "available", "device": stored.to_receipt()}
708
+
709
+
710
+ def _storage_error_payload(error: LoginError, *, source: str | None) -> dict[str, Any]:
711
+ return {
712
+ "schema_version": _STATUS_SCHEMA,
713
+ "status": "error",
714
+ "effective_source": source,
715
+ "stored_state": {"status": "unavailable", "error_code": error.code},
716
+ "remote_validation": "not_attempted",
717
+ "error_code": error.code,
718
+ }
719
+
720
+
721
+ def _devices_login_error(error: LoginError) -> dict[str, Any]:
722
+ return {
723
+ "schema_version": _DEVICES_SCHEMA,
724
+ "status": "error",
725
+ "effective_source": None,
726
+ "error_code": error.code,
727
+ }
728
+
729
+
730
+ def _reserved_lifecycle_payload(operation: str) -> dict[str, Any]:
731
+ return {
732
+ "schema_version": _STATUS_SCHEMA,
733
+ "status": "not_implemented",
734
+ "detail": f"auth {operation} is reserved for the remote-first credential lifecycle",
735
+ }
736
+
737
+
738
+ SPEC = CommandSpec(
739
+ name="auth",
740
+ help="inspect remotely validated device authority and token boundaries",
741
+ add_arguments=add_arguments,
742
+ run=run,
743
+ exit_code=exit_code,
744
+ )