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,1307 @@
1
+ """Separate-process review enrollment and signing authority for trusted H2 operators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import base64
7
+ import hashlib
8
+ import os
9
+ import re
10
+ import secrets
11
+ import select
12
+ import socket
13
+ import stat
14
+ import struct
15
+ import subprocess
16
+ import sys
17
+ import time
18
+ import uuid
19
+ from dataclasses import dataclass
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ from cryptography.exceptions import InvalidSignature
24
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
25
+ Ed25519PrivateKey,
26
+ Ed25519PublicKey,
27
+ )
28
+
29
+ from mostlyright.data_harness.canonical import (
30
+ CanonicalJSONError,
31
+ canonical_json_bytes,
32
+ parse_canonical_json,
33
+ )
34
+ from mostlyright.data_harness.offline import (
35
+ _H2_PRODUCER_ATTEMPT,
36
+ _H2_PRODUCER_PRINCIPAL,
37
+ review_target_path,
38
+ workspace_result_path, # noqa: F401 (re-exported: enrollment tests replay the workbench binding)
39
+ )
40
+ from mostlyright.data_harness.pipeline import verify_candidate
41
+ from mostlyright.data_harness.review import (
42
+ ASSIGNMENT_SCHEMA_VERSION,
43
+ REPORT_SCHEMA_VERSION,
44
+ ReviewError,
45
+ _candidate_artifact_paths,
46
+ _normalized_assignment_digest,
47
+ _parse_assignment,
48
+ load_public_key_base64,
49
+ public_key_to_base64,
50
+ )
51
+ from mostlyright.data_harness.signing import SignerProviderError
52
+
53
+ _REQUEST_SCHEMA_VERSION = "review-coordinator-request.v1"
54
+ _RESPONSE_SCHEMA_VERSION = "review-coordinator-response.v1"
55
+ _SESSION_SCHEMA_VERSION = "review-coordinator-session.v1"
56
+ _STARTUP_SCHEMA_VERSION = "review-coordinator-startup.v1"
57
+ _ANCHOR_SCHEMA_VERSION = "review-coordinator-anchor.v1"
58
+ _SEALED_ENROLLMENT_SCHEMA_VERSION = "review-coordinator-sealed-enrollment.v1"
59
+ _KEY_BUNDLE_SCHEMA_VERSION = "review-coordinator-key-bundle.v1"
60
+ _SYSTEM_ANCHOR_PATH = (
61
+ Path("/private/etc/mostlyright/trust/anchor.json")
62
+ if sys.platform == "darwin"
63
+ else Path("/etc/mostlyright/trust/anchor.json")
64
+ )
65
+ _ANCHOR_MODE = 0o444
66
+ _MAX_MESSAGE_BYTES = 1024 * 1024
67
+ _MAX_SIGNING_BYTES = 768 * 1024
68
+ _MAX_SESSION_REQUESTS = 32
69
+ _DESCRIPTOR_READ_TIMEOUT_SECONDS = 5.0
70
+ _SESSION_IDLE_TIMEOUT_SECONDS = 5.0
71
+ _FRAME_IO_TIMEOUT_SECONDS = 2.0
72
+ _CHILD_EXIT_TIMEOUT_SECONDS = 120.0
73
+ _CHILD_REAP_TIMEOUT_SECONDS = 2.0
74
+ _CHILD_KILL_ATTEMPTS = 3
75
+ _CHILD_REAP_ATTEMPTS = 3
76
+ _AUTHORITY_FD_ENV = "MOSTLYRIGHT_REVIEW_AUTHORITY_FD"
77
+ # Hex shape checks, applied beyond the fields they are named for: _REQUEST_ID
78
+ # also checks enrollment_id and anchor_id, and _NONCE also checks session ids and
79
+ # SHA-256 digests.
80
+ _REQUEST_ID = re.compile(r"^[0-9a-f]{32}$")
81
+ _NONCE = re.compile(r"^[0-9a-f]{64}$")
82
+ _HANDLE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]{31,127}$")
83
+ _IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$")
84
+ _REQUEST_FIELDS = {
85
+ "schema_version",
86
+ "session_id",
87
+ "request_id",
88
+ "client_nonce",
89
+ "operation",
90
+ "reviewer_attempt",
91
+ "payload",
92
+ }
93
+ _RESPONSE_BODY_FIELDS = {
94
+ "schema_version",
95
+ "session_id",
96
+ "request_id",
97
+ "client_nonce",
98
+ "server_nonce",
99
+ "ok",
100
+ "result",
101
+ "error",
102
+ }
103
+
104
+
105
+ class _CoordinatorProtocolError(RuntimeError):
106
+ """The coordinator transport or strict protocol failed closed."""
107
+
108
+
109
+ @dataclass(frozen=True)
110
+ class _TrustRecord:
111
+ """Client-side trust state taken from one session hello frame.
112
+
113
+ Only public_key is enforced afterwards: it verifies every response
114
+ signature. The enrollment fields record what the hello carried and bind
115
+ nothing later in the session.
116
+ """
117
+
118
+ public_key: Ed25519PublicKey
119
+ enrollment_id: str
120
+ enrollment_digest: str
121
+
122
+
123
+ @dataclass(frozen=True)
124
+ class _Enrollment:
125
+ """One signing role activated from the sealed enrollment and the key bundle.
126
+
127
+ role and reviewer_attempt decide which signing requests the session may
128
+ serve. handle_sha256 and lens record how the role was selected at activation
129
+ and are not rechecked per request.
130
+ """
131
+
132
+ handle_sha256: str
133
+ role: str
134
+ lens: str | None
135
+ reviewer_attempt: str | None
136
+ private_key: Ed25519PrivateKey
137
+
138
+
139
+ class _PeerClosed(RuntimeError):
140
+ """The only inherited session peer closed its descriptor."""
141
+
142
+
143
+ class _InheritedRemoteSigner:
144
+ """A role-bound signer proxy that never receives the coordinator private key."""
145
+
146
+ def __init__(
147
+ self,
148
+ provider: _InheritedReviewAuthorityProvider,
149
+ reviewer_attempt: str | None,
150
+ public_key: Ed25519PublicKey,
151
+ ) -> None:
152
+ self._provider = provider
153
+ self._reviewer_attempt = reviewer_attempt
154
+ self._public_key = public_key
155
+
156
+ def sign(self, data: bytes) -> bytes:
157
+ if not isinstance(data, bytes):
158
+ raise SignerProviderError("coordinator signer accepts exact bytes only")
159
+ result = self._provider._request(
160
+ operation="sign",
161
+ reviewer_attempt=self._reviewer_attempt,
162
+ payload=base64.b64encode(data).decode("ascii"),
163
+ )
164
+ if set(result) != {"signature"} or not isinstance(result["signature"], str):
165
+ raise SignerProviderError("coordinator returned an invalid signature response")
166
+ try:
167
+ signature = base64.b64decode(result["signature"], validate=True)
168
+ except (TypeError, ValueError) as exc:
169
+ raise SignerProviderError("coordinator returned invalid signature encoding") from exc
170
+ if len(signature) != 64:
171
+ raise SignerProviderError("coordinator returned an invalid Ed25519 signature")
172
+ try:
173
+ self._public_key.verify(signature, data)
174
+ except InvalidSignature as exc:
175
+ raise SignerProviderError(
176
+ "coordinator signature does not match the enrolled public key"
177
+ ) from exc
178
+ return signature
179
+
180
+ def public_key(self) -> Ed25519PublicKey:
181
+ return self._public_key
182
+
183
+
184
+ class _InheritedReviewAuthority:
185
+ """One role-scoped authority view over an inherited anonymous session."""
186
+
187
+ def __init__(self, provider: _InheritedReviewAuthorityProvider) -> None:
188
+ self._provider = provider
189
+
190
+ def assignment_value(self) -> dict[str, Any]:
191
+ result = self._provider._request(
192
+ operation="assignment",
193
+ reviewer_attempt=None,
194
+ payload=None,
195
+ )
196
+ if set(result) != {"assignment"} or not isinstance(result["assignment"], dict):
197
+ raise SignerProviderError("coordinator returned an invalid assignment response")
198
+ return result["assignment"]
199
+
200
+ def reviewer_signer_for(self, reviewer_attempt: str) -> _InheritedRemoteSigner:
201
+ if not isinstance(reviewer_attempt, str) or _IDENTIFIER.fullmatch(reviewer_attempt) is None:
202
+ raise SignerProviderError("reviewer attempt is not a valid identifier")
203
+ public_key = self._public_key_for(reviewer_attempt)
204
+ return _InheritedRemoteSigner(
205
+ self._provider,
206
+ reviewer_attempt,
207
+ public_key,
208
+ )
209
+
210
+ def verifier_signer(self) -> _InheritedRemoteSigner:
211
+ public_key = self._public_key_for(None)
212
+ return _InheritedRemoteSigner(self._provider, None, public_key)
213
+
214
+ def _public_key_for(self, reviewer_attempt: str | None) -> Ed25519PublicKey:
215
+ result = self._provider._request(
216
+ operation="public_key",
217
+ reviewer_attempt=reviewer_attempt,
218
+ payload=None,
219
+ )
220
+ if set(result) != {"public_key"} or not isinstance(result["public_key"], str):
221
+ raise SignerProviderError("coordinator returned an invalid public-key response")
222
+ try:
223
+ return load_public_key_base64(result["public_key"])
224
+ except Exception as exc:
225
+ raise SignerProviderError("coordinator returned an invalid public key") from exc
226
+
227
+
228
+ class _InheritedReviewAuthorityProvider:
229
+ """Resolve one role-scoped authority through a launcher-created socketpair."""
230
+
231
+ def __init__(self) -> None:
232
+ if os.name == "nt" or not hasattr(socket, "AF_UNIX"):
233
+ raise SignerProviderError("Unix review coordinator transport is unavailable")
234
+ descriptor = _take_inherited_authority_descriptor()
235
+ connection: socket.socket | None = None
236
+ try:
237
+ connection = socket.socket(fileno=descriptor)
238
+ if connection.family != socket.AF_UNIX:
239
+ raise _CoordinatorProtocolError("authority descriptor is not a Unix socket")
240
+ raw_hello = _receive_frame(
241
+ connection,
242
+ idle_timeout=_SESSION_IDLE_TIMEOUT_SECONDS,
243
+ )
244
+ self._trust, self._session_id = _parse_session_hello(raw_hello)
245
+ self._connection = connection
246
+ except Exception as exc:
247
+ if connection is None:
248
+ try:
249
+ os.close(descriptor)
250
+ except OSError:
251
+ pass
252
+ else:
253
+ connection.close()
254
+ raise SignerProviderError("inherited coordinator session is unavailable") from exc
255
+
256
+ def authority(self) -> _InheritedReviewAuthority:
257
+ return _InheritedReviewAuthority(self)
258
+
259
+ def _request(
260
+ self,
261
+ *,
262
+ operation: str,
263
+ reviewer_attempt: str | None,
264
+ payload: str | None,
265
+ ) -> dict[str, Any]:
266
+ request_id = uuid.uuid4().hex
267
+ client_nonce = secrets.token_hex(32)
268
+ request = {
269
+ "schema_version": _REQUEST_SCHEMA_VERSION,
270
+ "session_id": self._session_id,
271
+ "request_id": request_id,
272
+ "client_nonce": client_nonce,
273
+ "operation": operation,
274
+ "reviewer_attempt": reviewer_attempt,
275
+ "payload": payload,
276
+ }
277
+ try:
278
+ _send_frame(
279
+ self._connection,
280
+ canonical_json_bytes(request),
281
+ )
282
+ raw_response = _receive_frame(
283
+ self._connection,
284
+ idle_timeout=_SESSION_IDLE_TIMEOUT_SECONDS,
285
+ )
286
+ envelope = _parse_canonical_message(raw_response, "coordinator response")
287
+ return self._verify_response(
288
+ envelope,
289
+ request_id=request_id,
290
+ client_nonce=client_nonce,
291
+ )
292
+ except SignerProviderError:
293
+ raise
294
+ except Exception as exc:
295
+ raise SignerProviderError("coordinator request failed closed") from exc
296
+
297
+ def _verify_response(
298
+ self,
299
+ value: Any,
300
+ *,
301
+ request_id: str,
302
+ client_nonce: str,
303
+ ) -> dict[str, Any]:
304
+ if not isinstance(value, dict) or set(value) != {"body", "signature"}:
305
+ raise _CoordinatorProtocolError("coordinator response envelope fields are invalid")
306
+ body = value["body"]
307
+ signature_text = value["signature"]
308
+ if not isinstance(body, dict) or set(body) != _RESPONSE_BODY_FIELDS:
309
+ raise _CoordinatorProtocolError("coordinator response body fields are invalid")
310
+ if (
311
+ body["schema_version"] != _RESPONSE_SCHEMA_VERSION
312
+ or body["session_id"] != self._session_id
313
+ or body["request_id"] != request_id
314
+ or body["client_nonce"] != client_nonce
315
+ or not isinstance(body["server_nonce"], str)
316
+ or _NONCE.fullmatch(body["server_nonce"]) is None
317
+ or type(body["ok"]) is not bool
318
+ ):
319
+ raise _CoordinatorProtocolError("coordinator response binding is invalid")
320
+ if not isinstance(signature_text, str):
321
+ raise _CoordinatorProtocolError("coordinator response signature is invalid")
322
+ try:
323
+ signature = base64.b64decode(signature_text, validate=True)
324
+ except (TypeError, ValueError) as exc:
325
+ raise _CoordinatorProtocolError(
326
+ "coordinator response signature encoding is invalid"
327
+ ) from exc
328
+ try:
329
+ self._trust.public_key.verify(signature, canonical_json_bytes(body))
330
+ except InvalidSignature as exc:
331
+ raise _CoordinatorProtocolError(
332
+ "coordinator response root signature is invalid"
333
+ ) from exc
334
+
335
+ if body["ok"] is True:
336
+ if not isinstance(body["result"], dict) or body["error"] is not None:
337
+ raise _CoordinatorProtocolError("coordinator success response is invalid")
338
+ return body["result"]
339
+ error = body["error"]
340
+ if body["result"] is not None or not isinstance(error, dict):
341
+ raise _CoordinatorProtocolError("coordinator error response is invalid")
342
+ if set(error) != {"code", "message"} or not all(
343
+ isinstance(error[field], str) for field in ("code", "message")
344
+ ):
345
+ raise _CoordinatorProtocolError("coordinator error fields are invalid")
346
+ raise SignerProviderError(f"coordinator rejected request: {error['code']}")
347
+
348
+
349
+ class _CoordinatorSessionServer:
350
+ """Serve one socketpair session for the single role the child was launched as.
351
+
352
+ The first frame is a hello carrying the session id and the sealed
353
+ enrollment, from which the client derives the response root public key.
354
+ Every response after it is signed by that root key over its canonical body.
355
+ A request id or client nonce may be used once per session; a repeat is
356
+ answered with a replayed_request error rather than the operation. The
357
+ assignment is readable by any session, while public_key and sign are refused
358
+ unless the request names this session's own role. The session ends after
359
+ _MAX_SESSION_REQUESTS requests, at the child deadline, or when the peer
360
+ closes.
361
+ """
362
+
363
+ def __init__(
364
+ self,
365
+ *,
366
+ connection: socket.socket,
367
+ assignment: dict[str, Any],
368
+ enrollment: _Enrollment,
369
+ root_private_key: Ed25519PrivateKey,
370
+ sealed_enrollment: dict[str, Any],
371
+ ) -> None:
372
+ self.connection = connection
373
+ self.assignment = assignment
374
+ self.enrollment = enrollment
375
+ self.root_private_key = root_private_key
376
+ self.sealed_enrollment = sealed_enrollment
377
+ self.session_id = secrets.token_hex(32)
378
+ self._seen_request_ids: set[str] = set()
379
+ self._seen_client_nonces: set[str] = set()
380
+
381
+ def serve(self, *, deadline: float) -> None:
382
+ hello = {
383
+ "schema_version": _SESSION_SCHEMA_VERSION,
384
+ "session_id": self.session_id,
385
+ "sealed_enrollment": self.sealed_enrollment,
386
+ }
387
+ _send_frame(self.connection, canonical_json_bytes(hello))
388
+ for _request_number in range(_MAX_SESSION_REQUESTS):
389
+ remaining = deadline - time.monotonic()
390
+ if remaining <= 0:
391
+ raise TimeoutError("coordinator session deadline expired")
392
+ try:
393
+ raw_request = _receive_frame(
394
+ self.connection,
395
+ idle_timeout=min(_SESSION_IDLE_TIMEOUT_SECONDS, remaining),
396
+ )
397
+ except _PeerClosed:
398
+ return
399
+ request = _parse_request(raw_request)
400
+ if request["session_id"] != self.session_id:
401
+ raise _CoordinatorProtocolError("request is bound to another session")
402
+ _send_frame(self.connection, self._dispatch(request))
403
+ raise _CoordinatorProtocolError("coordinator session request limit exceeded")
404
+
405
+ def _dispatch(self, request: dict[str, Any]) -> bytes:
406
+ request_id = request["request_id"]
407
+ client_nonce = request["client_nonce"]
408
+ if request_id in self._seen_request_ids or client_nonce in self._seen_client_nonces:
409
+ return self._signed_response(
410
+ request_id,
411
+ client_nonce,
412
+ ok=False,
413
+ error_code="replayed_request",
414
+ error_message="request identity has already been used",
415
+ )
416
+ self._seen_request_ids.add(request_id)
417
+ self._seen_client_nonces.add(client_nonce)
418
+
419
+ operation = request["operation"]
420
+ reviewer_attempt = request["reviewer_attempt"]
421
+ if operation == "assignment":
422
+ return self._signed_response(
423
+ request_id,
424
+ client_nonce,
425
+ ok=True,
426
+ result={"assignment": self.assignment},
427
+ )
428
+ if not self._authorized_signer_request(self.enrollment, reviewer_attempt):
429
+ return self._signed_response(
430
+ request_id,
431
+ client_nonce,
432
+ ok=False,
433
+ error_code="role_not_authorized",
434
+ error_message="inherited session is not authorized for this signing role",
435
+ )
436
+ if operation == "public_key":
437
+ return self._signed_response(
438
+ request_id,
439
+ client_nonce,
440
+ ok=True,
441
+ result={
442
+ "public_key": public_key_to_base64(self.enrollment.private_key.public_key())
443
+ },
444
+ )
445
+ if operation == "sign":
446
+ try:
447
+ payload = base64.b64decode(request["payload"], validate=True)
448
+ except (TypeError, ValueError):
449
+ return self._signed_response(
450
+ request_id,
451
+ client_nonce,
452
+ ok=False,
453
+ error_code="invalid_payload",
454
+ error_message="signing payload encoding is invalid",
455
+ )
456
+ if len(payload) > _MAX_SIGNING_BYTES:
457
+ return self._signed_response(
458
+ request_id,
459
+ client_nonce,
460
+ ok=False,
461
+ error_code="payload_too_large",
462
+ error_message="signing payload exceeds the byte limit",
463
+ )
464
+ signature = self.enrollment.private_key.sign(payload)
465
+ return self._signed_response(
466
+ request_id,
467
+ client_nonce,
468
+ ok=True,
469
+ result={"signature": base64.b64encode(signature).decode("ascii")},
470
+ )
471
+ raise _CoordinatorProtocolError("validated request operation is unreachable")
472
+
473
+ @staticmethod
474
+ def _authorized_signer_request(
475
+ enrollment: _Enrollment,
476
+ reviewer_attempt: str | None,
477
+ ) -> bool:
478
+ if enrollment.role == "verifier":
479
+ return reviewer_attempt is None
480
+ return reviewer_attempt == enrollment.reviewer_attempt
481
+
482
+ def _signed_response(
483
+ self,
484
+ request_id: str,
485
+ client_nonce: str,
486
+ *,
487
+ ok: bool,
488
+ result: dict[str, Any] | None = None,
489
+ error_code: str | None = None,
490
+ error_message: str | None = None,
491
+ ) -> bytes:
492
+ body = {
493
+ "schema_version": _RESPONSE_SCHEMA_VERSION,
494
+ "session_id": self.session_id,
495
+ "request_id": request_id,
496
+ "client_nonce": client_nonce,
497
+ "server_nonce": secrets.token_hex(32),
498
+ "ok": ok,
499
+ "result": result if ok else None,
500
+ "error": (
501
+ None
502
+ if ok
503
+ else {
504
+ "code": error_code,
505
+ "message": error_message,
506
+ }
507
+ ),
508
+ }
509
+ signature = self.root_private_key.sign(canonical_json_bytes(body))
510
+ response = {
511
+ "body": body,
512
+ "signature": base64.b64encode(signature).decode("ascii"),
513
+ }
514
+ encoded = canonical_json_bytes(response)
515
+ if len(encoded) > _MAX_MESSAGE_BYTES:
516
+ raise _CoordinatorProtocolError("coordinator response exceeds the byte limit")
517
+ return encoded
518
+
519
+
520
+ def coordinator_main(argv: list[str] | None = None) -> int:
521
+ """Prepare metadata or launch one role-scoped anonymous authority session."""
522
+
523
+ parser = argparse.ArgumentParser(prog="mr-data-coordinator")
524
+ subparsers = parser.add_subparsers(dest="command", required=True)
525
+ prepare = subparsers.add_parser("prepare")
526
+ prepare.add_argument("--workspace", required=True)
527
+ prepare.add_argument("--sealed-enrollment-fd", type=_descriptor_argument, required=True)
528
+ run = subparsers.add_parser("run")
529
+ run.add_argument("--workspace", required=True)
530
+ run.add_argument("--sealed-enrollment-fd", type=_descriptor_argument, required=True)
531
+ run.add_argument("--key-bundle-fd", type=_descriptor_argument, required=True)
532
+ run.add_argument("--role-token-fd", type=_descriptor_argument, required=True)
533
+ run.add_argument("child_command", nargs=argparse.REMAINDER)
534
+ args = parser.parse_args(argv)
535
+ if os.name == "nt" or not hasattr(socket, "AF_UNIX"):
536
+ parser.error("Unix socketpair transport is required")
537
+
538
+ workspace = Path(os.path.abspath(args.workspace))
539
+ try:
540
+ candidate = verify_candidate(review_target_path(workspace))
541
+ descriptor_values = [args.sealed_enrollment_fd]
542
+ if args.command == "run":
543
+ descriptor_values.extend([args.key_bundle_fd, args.role_token_fd])
544
+ if len(set(descriptor_values)) != len(descriptor_values):
545
+ raise _CoordinatorProtocolError("coordinator input descriptors must be distinct")
546
+ sealed_enrollment = _read_canonical_descriptor(
547
+ args.sealed_enrollment_fd,
548
+ label="sealed enrollment",
549
+ )
550
+ if args.command == "prepare":
551
+ assignment, enrollment_id = _prepare_enrollment(
552
+ candidate,
553
+ sealed_enrollment=sealed_enrollment,
554
+ )
555
+ startup = {
556
+ "schema_version": _STARTUP_SCHEMA_VERSION,
557
+ "assignment": assignment,
558
+ "report_templates": _report_templates(candidate, assignment),
559
+ "enrollment_id": enrollment_id,
560
+ }
561
+ sys.stdout.buffer.write(canonical_json_bytes(startup) + b"\n")
562
+ sys.stdout.buffer.flush()
563
+ return 0
564
+
565
+ key_bundle = _read_canonical_descriptor(
566
+ args.key_bundle_fd,
567
+ label="coordinator key bundle",
568
+ )
569
+ role_token_commitment = _read_role_token_commitment_descriptor(args.role_token_fd)
570
+ child_command = list(args.child_command)
571
+ if child_command and child_command[0] == "--":
572
+ child_command.pop(0)
573
+ if not child_command:
574
+ raise _CoordinatorProtocolError("coordinator child command is required")
575
+ assignment, enrollments, root_private_key, _enrollment_id = _activate_enrollment(
576
+ candidate,
577
+ sealed_enrollment=sealed_enrollment,
578
+ key_bundle=key_bundle,
579
+ )
580
+ enrollment = enrollments.get(role_token_commitment)
581
+ if enrollment is None:
582
+ raise _CoordinatorProtocolError("role token is not enrolled")
583
+ return _run_authorized_child(
584
+ child_command,
585
+ assignment=assignment,
586
+ enrollment=enrollment,
587
+ root_private_key=root_private_key,
588
+ sealed_enrollment=sealed_enrollment,
589
+ )
590
+ except (
591
+ CanonicalJSONError,
592
+ OSError,
593
+ subprocess.SubprocessError,
594
+ TimeoutError,
595
+ ValueError,
596
+ ReviewError,
597
+ SignerProviderError,
598
+ _CoordinatorProtocolError,
599
+ ) as exc:
600
+ print(f"coordinator startup failed: {type(exc).__name__}", file=sys.stderr)
601
+ return 2
602
+
603
+
604
+ def _prepare_enrollment(
605
+ candidate: Any,
606
+ *,
607
+ sealed_enrollment: Any,
608
+ ) -> tuple[dict[str, Any], str]:
609
+ body = _verified_enrollment_body(candidate, sealed_enrollment)
610
+ return _assignment_from_enrollment_body(body), body["enrollment_id"]
611
+
612
+
613
+ def _activate_enrollment(
614
+ candidate: Any,
615
+ *,
616
+ sealed_enrollment: Any,
617
+ key_bundle: Any,
618
+ ) -> tuple[
619
+ dict[str, Any],
620
+ dict[str, _Enrollment],
621
+ Ed25519PrivateKey,
622
+ str,
623
+ ]:
624
+ body = _verified_enrollment_body(candidate, sealed_enrollment)
625
+ assignment = _assignment_from_enrollment_body(body)
626
+ bundle = _parse_key_bundle(key_bundle, enrollment_id=body["enrollment_id"])
627
+ root_private_key = bundle["response_root_private_key"]
628
+ if public_key_to_base64(root_private_key.public_key()) != body["response_root_public_key"]:
629
+ raise _CoordinatorProtocolError(
630
+ "coordinator response key does not match the sealed enrollment"
631
+ )
632
+ enrollments: dict[str, _Enrollment] = {}
633
+ for reviewer in body["reviewers"]:
634
+ lens = reviewer["lens"]
635
+ key = bundle["reviewer_private_keys"][lens]
636
+ if public_key_to_base64(key.public_key()) != reviewer["public_key"]:
637
+ raise _CoordinatorProtocolError(
638
+ "reviewer key bundle does not match the sealed enrollment"
639
+ )
640
+ handle_sha256 = reviewer["handle_sha256"]
641
+ enrollments[handle_sha256] = _Enrollment(
642
+ handle_sha256=handle_sha256,
643
+ role="reviewer",
644
+ lens=lens,
645
+ reviewer_attempt=reviewer["attempt_id"],
646
+ private_key=key,
647
+ )
648
+ verifier_key = bundle["verifier_private_key"]
649
+ if public_key_to_base64(verifier_key.public_key()) != body["verifier"]["public_key"]:
650
+ raise _CoordinatorProtocolError("verifier key bundle does not match the sealed enrollment")
651
+ verifier_handle_sha256 = body["verifier"]["handle_sha256"]
652
+ enrollments[verifier_handle_sha256] = _Enrollment(
653
+ handle_sha256=verifier_handle_sha256,
654
+ role="verifier",
655
+ lens=None,
656
+ reviewer_attempt=None,
657
+ private_key=verifier_key,
658
+ )
659
+ if len(enrollments) != 3:
660
+ raise _CoordinatorProtocolError("sealed enrollment has duplicate role handles")
661
+ return assignment, enrollments, root_private_key, body["enrollment_id"]
662
+
663
+
664
+ def _verified_enrollment_body(candidate: Any, sealed_enrollment: Any) -> dict[str, Any]:
665
+ body = _verify_sealed_enrollment(sealed_enrollment, _load_anchor_public_key())
666
+ trusted_producer = {
667
+ "principal_id": _H2_PRODUCER_PRINCIPAL,
668
+ "attempt_id": _H2_PRODUCER_ATTEMPT,
669
+ }
670
+ if (
671
+ body["candidate_digest"] != candidate.candidate_digest
672
+ or body["validation_policy_digest"] != candidate.validation_policy_digest
673
+ or body["producer"] != trusted_producer
674
+ or {
675
+ "principal_id": candidate.producer_principal,
676
+ "attempt_id": candidate.producer_attempt,
677
+ }
678
+ != trusted_producer
679
+ ):
680
+ raise _CoordinatorProtocolError(
681
+ "sealed enrollment does not bind the exact candidate and fixed H2 producer"
682
+ )
683
+ return body
684
+
685
+
686
+ def _assignment_from_enrollment_body(body: dict[str, Any]) -> dict[str, Any]:
687
+ reviewers = [
688
+ {key: value for key, value in reviewer.items() if key != "handle_sha256"}
689
+ for reviewer in body["reviewers"]
690
+ ]
691
+ assignment = _parse_assignment(
692
+ {
693
+ "schema_version": ASSIGNMENT_SCHEMA_VERSION,
694
+ "candidate_digest": body["candidate_digest"],
695
+ "validation_policy_digest": body["validation_policy_digest"],
696
+ "cycle_lineage": body["cycle_lineage"],
697
+ "failed_cycle_count": 0,
698
+ "producer": body["producer"],
699
+ "reviewers": reviewers,
700
+ "verifier": {
701
+ "principal_id": body["verifier"]["principal_id"],
702
+ "public_key": body["verifier"]["public_key"],
703
+ },
704
+ }
705
+ )
706
+ return assignment
707
+
708
+
709
+ def _verify_sealed_enrollment(
710
+ value: Any,
711
+ anchor_public_key: Ed25519PublicKey,
712
+ ) -> dict[str, Any]:
713
+ if not isinstance(value, dict) or set(value) != {"body", "signature"}:
714
+ raise _CoordinatorProtocolError("sealed enrollment envelope is invalid")
715
+ body = value["body"]
716
+ signature_text = value["signature"]
717
+ if not isinstance(body, dict) or set(body) != {
718
+ "schema_version",
719
+ "enrollment_id",
720
+ "candidate_digest",
721
+ "validation_policy_digest",
722
+ "cycle_lineage",
723
+ "producer",
724
+ "reviewers",
725
+ "verifier",
726
+ "response_root_public_key",
727
+ }:
728
+ raise _CoordinatorProtocolError("sealed enrollment fields are invalid")
729
+ if (
730
+ body["schema_version"] != _SEALED_ENROLLMENT_SCHEMA_VERSION
731
+ or not isinstance(body["enrollment_id"], str)
732
+ or _REQUEST_ID.fullmatch(body["enrollment_id"]) is None
733
+ or not isinstance(body["candidate_digest"], str)
734
+ or _NONCE.fullmatch(body["candidate_digest"]) is None
735
+ or not isinstance(body["validation_policy_digest"], str)
736
+ or _NONCE.fullmatch(body["validation_policy_digest"]) is None
737
+ or not isinstance(body["cycle_lineage"], str)
738
+ or _IDENTIFIER.fullmatch(body["cycle_lineage"]) is None
739
+ or not isinstance(body["producer"], dict)
740
+ or set(body["producer"]) != {"principal_id", "attempt_id"}
741
+ ):
742
+ raise _CoordinatorProtocolError("sealed enrollment binding is invalid")
743
+ reviewers = body["reviewers"]
744
+ if not isinstance(reviewers, list) or len(reviewers) != 2:
745
+ raise _CoordinatorProtocolError("sealed enrollment reviewer set is invalid")
746
+ expected_reviewer_fields = {
747
+ "principal_id",
748
+ "attempt_id",
749
+ "lens",
750
+ "key_id",
751
+ "public_key",
752
+ "handle_sha256",
753
+ }
754
+ for reviewer in reviewers:
755
+ if (
756
+ not isinstance(reviewer, dict)
757
+ or set(reviewer) != expected_reviewer_fields
758
+ or not isinstance(reviewer["handle_sha256"], str)
759
+ or _NONCE.fullmatch(reviewer["handle_sha256"]) is None
760
+ ):
761
+ raise _CoordinatorProtocolError("sealed enrollment reviewer entry is invalid")
762
+ verifier = body["verifier"]
763
+ if (
764
+ not isinstance(verifier, dict)
765
+ or set(verifier) != {"principal_id", "public_key", "handle_sha256"}
766
+ or not isinstance(verifier["handle_sha256"], str)
767
+ or _NONCE.fullmatch(verifier["handle_sha256"]) is None
768
+ or not isinstance(body["response_root_public_key"], str)
769
+ ):
770
+ raise _CoordinatorProtocolError("sealed enrollment verifier entry is invalid")
771
+ role_hashes = [
772
+ *(reviewer["handle_sha256"] for reviewer in reviewers),
773
+ verifier["handle_sha256"],
774
+ ]
775
+ if len(set(role_hashes)) != 3:
776
+ raise _CoordinatorProtocolError("sealed enrollment role handle hashes must be distinct")
777
+ role_public_keys = [
778
+ *(reviewer["public_key"] for reviewer in reviewers),
779
+ verifier["public_key"],
780
+ ]
781
+ for public_key in [*role_public_keys, body["response_root_public_key"]]:
782
+ load_public_key_base64(public_key)
783
+ if len(set(role_public_keys)) != 3 or body["response_root_public_key"] in role_public_keys:
784
+ raise _CoordinatorProtocolError("sealed enrollment signing keys must be distinct")
785
+ if not isinstance(signature_text, str):
786
+ raise _CoordinatorProtocolError("sealed enrollment signature is invalid")
787
+ try:
788
+ signature = base64.b64decode(signature_text, validate=True)
789
+ anchor_public_key.verify(signature, canonical_json_bytes(body))
790
+ except (InvalidSignature, TypeError, ValueError) as exc:
791
+ raise _CoordinatorProtocolError(
792
+ "sealed enrollment signature is not from the trusted anchor"
793
+ ) from exc
794
+ return body
795
+
796
+
797
+ def _parse_key_bundle(
798
+ value: Any,
799
+ *,
800
+ enrollment_id: str,
801
+ ) -> dict[str, Any]:
802
+ if not isinstance(value, dict) or set(value) != {
803
+ "schema_version",
804
+ "enrollment_id",
805
+ "response_root_private_key",
806
+ "reviewer_private_keys",
807
+ "verifier_private_key",
808
+ }:
809
+ raise _CoordinatorProtocolError("coordinator key bundle fields are invalid")
810
+ if (
811
+ value["schema_version"] != _KEY_BUNDLE_SCHEMA_VERSION
812
+ or value["enrollment_id"] != enrollment_id
813
+ or not isinstance(value["reviewer_private_keys"], dict)
814
+ or set(value["reviewer_private_keys"])
815
+ != {"question_source_fit", "transformation_data_quality"}
816
+ ):
817
+ raise _CoordinatorProtocolError("coordinator key bundle binding is invalid")
818
+ return {
819
+ "response_root_private_key": _load_private_key(value["response_root_private_key"]),
820
+ "reviewer_private_keys": {
821
+ lens: _load_private_key(encoded)
822
+ for lens, encoded in value["reviewer_private_keys"].items()
823
+ },
824
+ "verifier_private_key": _load_private_key(value["verifier_private_key"]),
825
+ }
826
+
827
+
828
+ def _load_private_key(value: Any) -> Ed25519PrivateKey:
829
+ if not isinstance(value, str):
830
+ raise _CoordinatorProtocolError("coordinator private key encoding is invalid")
831
+ try:
832
+ raw = base64.b64decode(value, validate=True)
833
+ if len(raw) != 32:
834
+ raise ValueError
835
+ return Ed25519PrivateKey.from_private_bytes(raw)
836
+ except (TypeError, ValueError) as exc:
837
+ raise _CoordinatorProtocolError("coordinator private key encoding is invalid") from exc
838
+
839
+
840
+ def _report_templates(candidate: Any, assignment: dict[str, Any]) -> dict[str, Any]:
841
+ assignment_digest = _normalized_assignment_digest(assignment)
842
+ reviewed_paths = _candidate_artifact_paths(candidate)
843
+ return {
844
+ reviewer["lens"]: {
845
+ "schema_version": REPORT_SCHEMA_VERSION,
846
+ "assignment_digest": assignment_digest,
847
+ "candidate_digest": assignment["candidate_digest"],
848
+ "validation_policy_digest": assignment["validation_policy_digest"],
849
+ "cycle_lineage": assignment["cycle_lineage"],
850
+ "failed_cycle_count": assignment["failed_cycle_count"],
851
+ "reviewer_principal": reviewer["principal_id"],
852
+ "reviewer_attempt": reviewer["attempt_id"],
853
+ "reviewer_key_id": reviewer["key_id"],
854
+ "lens": reviewer["lens"],
855
+ "reviewed_artifact_paths": reviewed_paths,
856
+ "summary": "REPLACE_WITH_INDEPENDENT_REVIEW_SUMMARY",
857
+ "findings": [],
858
+ }
859
+ for reviewer in assignment["reviewers"]
860
+ }
861
+
862
+
863
+ def _parse_request(raw: bytes) -> dict[str, Any]:
864
+ value = _parse_canonical_message(raw, "coordinator request")
865
+ if not isinstance(value, dict) or set(value) != _REQUEST_FIELDS:
866
+ raise _CoordinatorProtocolError("coordinator request fields are invalid")
867
+ if (
868
+ value["schema_version"] != _REQUEST_SCHEMA_VERSION
869
+ or not isinstance(value["session_id"], str)
870
+ or _NONCE.fullmatch(value["session_id"]) is None
871
+ or not isinstance(value["request_id"], str)
872
+ or _REQUEST_ID.fullmatch(value["request_id"]) is None
873
+ or not isinstance(value["client_nonce"], str)
874
+ or _NONCE.fullmatch(value["client_nonce"]) is None
875
+ or value["operation"] not in {"assignment", "public_key", "sign"}
876
+ ):
877
+ raise _CoordinatorProtocolError("coordinator request binding is invalid")
878
+ operation = value["operation"]
879
+ reviewer_attempt = value["reviewer_attempt"]
880
+ payload = value["payload"]
881
+ if reviewer_attempt is not None and (
882
+ not isinstance(reviewer_attempt, str) or _IDENTIFIER.fullmatch(reviewer_attempt) is None
883
+ ):
884
+ raise _CoordinatorProtocolError("coordinator reviewer attempt is invalid")
885
+ if operation == "assignment":
886
+ if reviewer_attempt is not None or payload is not None:
887
+ raise _CoordinatorProtocolError("assignment request contains signer fields")
888
+ elif operation == "public_key":
889
+ if payload is not None:
890
+ raise _CoordinatorProtocolError("public-key request contains a payload")
891
+ elif not isinstance(payload, str):
892
+ raise _CoordinatorProtocolError("sign request payload is invalid")
893
+ return value
894
+
895
+
896
+ def _parse_session_hello(raw: bytes) -> tuple[_TrustRecord, str]:
897
+ value = _parse_canonical_message(raw, "coordinator session hello")
898
+ if not isinstance(value, dict) or set(value) != {
899
+ "schema_version",
900
+ "session_id",
901
+ "sealed_enrollment",
902
+ }:
903
+ raise _CoordinatorProtocolError("coordinator session hello fields are invalid")
904
+ session_id = value["session_id"]
905
+ if (
906
+ value["schema_version"] != _SESSION_SCHEMA_VERSION
907
+ or not isinstance(session_id, str)
908
+ or _NONCE.fullmatch(session_id) is None
909
+ ):
910
+ raise _CoordinatorProtocolError("coordinator session hello binding is invalid")
911
+ enrollment_body = _verify_sealed_enrollment(
912
+ value["sealed_enrollment"],
913
+ _load_anchor_public_key(),
914
+ )
915
+ public_key = load_public_key_base64(enrollment_body["response_root_public_key"])
916
+ return (
917
+ _TrustRecord(
918
+ public_key=public_key,
919
+ enrollment_id=enrollment_body["enrollment_id"],
920
+ enrollment_digest=hashlib.sha256(
921
+ canonical_json_bytes(value["sealed_enrollment"])
922
+ ).hexdigest(),
923
+ ),
924
+ session_id,
925
+ )
926
+
927
+
928
+ def _send_frame(connection: socket.socket, payload: bytes) -> None:
929
+ if not payload or len(payload) > _MAX_MESSAGE_BYTES:
930
+ raise _CoordinatorProtocolError("coordinator frame size is invalid")
931
+ framed = struct.pack("!I", len(payload)) + payload
932
+ deadline = time.monotonic() + _FRAME_IO_TIMEOUT_SECONDS
933
+ offset = 0
934
+ while offset < len(framed):
935
+ remaining = deadline - time.monotonic()
936
+ if remaining <= 0:
937
+ raise TimeoutError("coordinator frame write deadline expired")
938
+ _readable, writable, _exceptional = select.select([], [connection], [], remaining)
939
+ if not writable:
940
+ raise TimeoutError("coordinator frame write deadline expired")
941
+ try:
942
+ written = connection.send(framed[offset:])
943
+ except BlockingIOError:
944
+ continue
945
+ if written <= 0:
946
+ raise _PeerClosed("coordinator session peer closed during frame write")
947
+ offset += written
948
+
949
+
950
+ def _receive_frame(
951
+ connection: socket.socket,
952
+ *,
953
+ idle_timeout: float,
954
+ ) -> bytes:
955
+ header = _receive_exact(connection, 4, timeout=idle_timeout)
956
+ (length,) = struct.unpack("!I", header)
957
+ if length <= 0 or length > _MAX_MESSAGE_BYTES:
958
+ raise _CoordinatorProtocolError("coordinator frame size is invalid")
959
+ return _receive_exact(
960
+ connection,
961
+ length,
962
+ timeout=_FRAME_IO_TIMEOUT_SECONDS,
963
+ )
964
+
965
+
966
+ def _receive_exact(connection: socket.socket, size: int, *, timeout: float) -> bytes:
967
+ deadline = time.monotonic() + timeout
968
+ chunks: list[bytes] = []
969
+ remaining_bytes = size
970
+ while remaining_bytes:
971
+ remaining_time = deadline - time.monotonic()
972
+ if remaining_time <= 0:
973
+ raise TimeoutError("coordinator frame read deadline expired")
974
+ readable, _writable, _exceptional = select.select([connection], [], [], remaining_time)
975
+ if not readable:
976
+ raise TimeoutError("coordinator frame read deadline expired")
977
+ try:
978
+ chunk = connection.recv(remaining_bytes)
979
+ except BlockingIOError:
980
+ continue
981
+ except ConnectionResetError as exc:
982
+ # A peer that exits with unread frames still buffered resets the socketpair
983
+ # on Linux, where macOS delivers plain EOF for the same abandonment; the two
984
+ # kernels must share one contract, so reset-at-frame-start is the peer
985
+ # closing and reset-mid-frame is the same truncation EOF would be.
986
+ if remaining_bytes == size:
987
+ raise _PeerClosed("coordinator session peer closed") from exc
988
+ raise _CoordinatorProtocolError("coordinator frame was truncated") from exc
989
+ if not chunk:
990
+ if remaining_bytes == size:
991
+ raise _PeerClosed("coordinator session peer closed")
992
+ raise _CoordinatorProtocolError("coordinator frame was truncated")
993
+ chunks.append(chunk)
994
+ remaining_bytes -= len(chunk)
995
+ return b"".join(chunks)
996
+
997
+
998
+ def _parse_canonical_message(raw: bytes, label: str) -> Any:
999
+ if not raw or len(raw) > _MAX_MESSAGE_BYTES:
1000
+ raise _CoordinatorProtocolError(f"{label} exceeds the byte limit")
1001
+ try:
1002
+ return parse_canonical_json(raw)
1003
+ except CanonicalJSONError as exc:
1004
+ raise _CoordinatorProtocolError(f"{label} is not strict canonical JSON") from exc
1005
+
1006
+
1007
+ def _inherited_authority_available() -> bool:
1008
+ """Return whether a launcher supplied the non-secret session descriptor number."""
1009
+
1010
+ return _AUTHORITY_FD_ENV in os.environ
1011
+
1012
+
1013
+ def _take_inherited_authority_descriptor() -> int:
1014
+ raw = os.environ.pop(_AUTHORITY_FD_ENV, None)
1015
+ if raw is None:
1016
+ raise _CoordinatorProtocolError("inherited authority descriptor is absent")
1017
+ descriptor = _descriptor_argument(raw)
1018
+ try:
1019
+ duplicate = os.dup(descriptor)
1020
+ finally:
1021
+ os.close(descriptor)
1022
+ return duplicate
1023
+
1024
+
1025
+ def _descriptor_argument(value: str) -> int:
1026
+ if not isinstance(value, str) or not value.isascii() or not value.isdecimal():
1027
+ raise argparse.ArgumentTypeError("descriptor must be a decimal integer")
1028
+ descriptor = int(value)
1029
+ if descriptor < 3:
1030
+ raise argparse.ArgumentTypeError("descriptor must not be stdin, stdout, or stderr")
1031
+ return descriptor
1032
+
1033
+
1034
+ def _read_canonical_descriptor(descriptor: int, *, label: str) -> Any:
1035
+ raw = _read_descriptor_bytes(
1036
+ descriptor,
1037
+ label=label,
1038
+ limit=_MAX_MESSAGE_BYTES,
1039
+ )
1040
+ try:
1041
+ return parse_canonical_json(raw)
1042
+ except CanonicalJSONError as exc:
1043
+ raise _CoordinatorProtocolError(
1044
+ f"coordinator {label} is not strict canonical JSON"
1045
+ ) from exc
1046
+
1047
+
1048
+ def _read_role_token_commitment_descriptor(descriptor: int) -> str:
1049
+ raw = _read_descriptor_bytes(descriptor, label="role token", limit=256)
1050
+ if not raw.endswith(b"\n") or raw.count(b"\n") != 1:
1051
+ raise _CoordinatorProtocolError("role token must be exactly one newline-terminated value")
1052
+ token = raw[:-1]
1053
+ try:
1054
+ token_text = token.decode("ascii")
1055
+ except UnicodeDecodeError as exc:
1056
+ raise _CoordinatorProtocolError("role token is not ASCII") from exc
1057
+ if _HANDLE.fullmatch(token_text) is None:
1058
+ raise _CoordinatorProtocolError("role token has an invalid format")
1059
+ return hashlib.sha256(token).hexdigest()
1060
+
1061
+
1062
+ def _read_descriptor_bytes(descriptor: int, *, label: str, limit: int) -> bytes:
1063
+ deadline = time.monotonic() + _DESCRIPTOR_READ_TIMEOUT_SECONDS
1064
+ chunks: list[bytes] = []
1065
+ total = 0
1066
+ try:
1067
+ os.fstat(descriptor)
1068
+ while True:
1069
+ remaining = deadline - time.monotonic()
1070
+ if remaining <= 0:
1071
+ raise TimeoutError(f"coordinator {label} read deadline expired")
1072
+ readable, _writable, _exceptional = select.select([descriptor], [], [], remaining)
1073
+ if not readable:
1074
+ raise TimeoutError(f"coordinator {label} read deadline expired")
1075
+ chunk = os.read(descriptor, min(65_536, limit + 1 - total))
1076
+ if not chunk:
1077
+ break
1078
+ chunks.append(chunk)
1079
+ total += len(chunk)
1080
+ if total > limit:
1081
+ raise _CoordinatorProtocolError(f"coordinator {label} exceeds the byte limit")
1082
+ finally:
1083
+ os.close(descriptor)
1084
+ raw = b"".join(chunks)
1085
+ if not raw:
1086
+ raise _CoordinatorProtocolError(f"coordinator {label} is empty")
1087
+ return raw
1088
+
1089
+
1090
+ def _run_authorized_child(
1091
+ child_command: list[str],
1092
+ *,
1093
+ assignment: dict[str, Any],
1094
+ enrollment: _Enrollment,
1095
+ root_private_key: Ed25519PrivateKey,
1096
+ sealed_enrollment: dict[str, Any],
1097
+ ) -> int:
1098
+ server_socket, child_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
1099
+ server = _CoordinatorSessionServer(
1100
+ connection=server_socket,
1101
+ assignment=assignment,
1102
+ enrollment=enrollment,
1103
+ root_private_key=root_private_key,
1104
+ sealed_enrollment=sealed_enrollment,
1105
+ )
1106
+ child_environment = os.environ.copy()
1107
+ child_environment.pop(_AUTHORITY_FD_ENV, None)
1108
+ child_environment[_AUTHORITY_FD_ENV] = str(child_socket.fileno())
1109
+ process: subprocess.Popen[bytes] | None = None
1110
+ deadline = time.monotonic() + _CHILD_EXIT_TIMEOUT_SECONDS
1111
+ try:
1112
+ process = subprocess.Popen(
1113
+ child_command,
1114
+ env=child_environment,
1115
+ close_fds=True,
1116
+ pass_fds=(child_socket.fileno(),),
1117
+ )
1118
+ child_socket.close()
1119
+ server.serve(deadline=deadline)
1120
+ try:
1121
+ remaining = deadline - time.monotonic()
1122
+ if remaining <= 0:
1123
+ raise subprocess.TimeoutExpired(child_command, _CHILD_EXIT_TIMEOUT_SECONDS)
1124
+ return_code = process.wait(timeout=remaining)
1125
+ except subprocess.TimeoutExpired as exc:
1126
+ raise TimeoutError("authorized child exceeded its exit deadline") from exc
1127
+ except BaseException:
1128
+ if process is not None:
1129
+ try:
1130
+ _terminate_child(process)
1131
+ except BaseException:
1132
+ pass
1133
+ try:
1134
+ _close_session_sockets(child_socket, server_socket)
1135
+ except BaseException:
1136
+ pass
1137
+ raise
1138
+ _close_session_sockets(child_socket, server_socket)
1139
+ return return_code
1140
+
1141
+
1142
+ def _terminate_child(process: subprocess.Popen[bytes]) -> None:
1143
+ first_failure: BaseException | None = None
1144
+
1145
+ def record_failure(exc: BaseException) -> None:
1146
+ nonlocal first_failure
1147
+ if first_failure is None:
1148
+ first_failure = exc
1149
+
1150
+ def reap_child(*, attempts: int) -> bool:
1151
+ for _attempt in range(attempts):
1152
+ try:
1153
+ process.wait(timeout=_CHILD_REAP_TIMEOUT_SECONDS)
1154
+ except subprocess.TimeoutExpired:
1155
+ pass
1156
+ except BaseException as exc:
1157
+ record_failure(exc)
1158
+ else:
1159
+ return True
1160
+
1161
+ try:
1162
+ return_code = process.poll()
1163
+ except BaseException as exc:
1164
+ record_failure(exc)
1165
+ else:
1166
+ if return_code is not None:
1167
+ return True
1168
+ return False
1169
+
1170
+ try:
1171
+ process.terminate()
1172
+ except BaseException as exc:
1173
+ record_failure(exc)
1174
+
1175
+ if reap_child(attempts=1):
1176
+ if first_failure is not None:
1177
+ raise first_failure
1178
+ return
1179
+
1180
+ for _attempt in range(_CHILD_KILL_ATTEMPTS):
1181
+ try:
1182
+ process.kill()
1183
+ except BaseException as exc:
1184
+ record_failure(exc)
1185
+ continue
1186
+ break
1187
+
1188
+ if reap_child(attempts=_CHILD_REAP_ATTEMPTS):
1189
+ if first_failure is not None:
1190
+ raise first_failure
1191
+ return
1192
+
1193
+ if first_failure is not None:
1194
+ raise first_failure
1195
+ raise TimeoutError("authorized child could not be killed and reaped")
1196
+
1197
+
1198
+ def _close_session_sockets(*connections: socket.socket) -> None:
1199
+ first_failure: BaseException | None = None
1200
+ for connection in connections:
1201
+ try:
1202
+ connection.close()
1203
+ except BaseException as exc:
1204
+ if first_failure is None:
1205
+ first_failure = exc
1206
+ if first_failure is not None:
1207
+ raise first_failure
1208
+
1209
+
1210
+ def _load_anchor_public_key() -> Ed25519PublicKey:
1211
+ anchor_path = (
1212
+ Path("/private/etc/mostlyright/trust/anchor.json")
1213
+ if sys.platform == "darwin"
1214
+ else Path("/etc/mostlyright/trust/anchor.json")
1215
+ )
1216
+ value = _read_system_anchor(
1217
+ anchor_path,
1218
+ owner_uid=0,
1219
+ )
1220
+ if not isinstance(value, dict) or set(value) != {
1221
+ "schema_version",
1222
+ "algorithm",
1223
+ "anchor_id",
1224
+ "public_key",
1225
+ }:
1226
+ raise _CoordinatorProtocolError("coordinator trust anchor fields are invalid")
1227
+ if (
1228
+ value["schema_version"] != _ANCHOR_SCHEMA_VERSION
1229
+ or value["algorithm"] != "Ed25519"
1230
+ or not isinstance(value["anchor_id"], str)
1231
+ or _REQUEST_ID.fullmatch(value["anchor_id"]) is None
1232
+ or not isinstance(value["public_key"], str)
1233
+ ):
1234
+ raise _CoordinatorProtocolError("coordinator trust anchor binding is invalid")
1235
+ return load_public_key_base64(value["public_key"])
1236
+
1237
+
1238
+ def _read_system_anchor(path: Path, *, owner_uid: int) -> Any:
1239
+ if not isinstance(path, Path) or not path.is_absolute():
1240
+ raise _CoordinatorProtocolError("coordinator system anchor path is not fixed and absolute")
1241
+ if type(owner_uid) is not int or owner_uid < 0:
1242
+ raise _CoordinatorProtocolError("coordinator system anchor owner is invalid")
1243
+ absolute = Path(os.path.abspath(os.fspath(path)))
1244
+ current = Path(absolute.anchor)
1245
+ for component in absolute.parts[1:-1]:
1246
+ current /= component
1247
+ info = os.lstat(current)
1248
+ if (
1249
+ not stat.S_ISDIR(info.st_mode)
1250
+ or info.st_uid not in {0, owner_uid}
1251
+ or stat.S_IMODE(info.st_mode) & 0o022
1252
+ ):
1253
+ raise _CoordinatorProtocolError(
1254
+ "coordinator system anchor ancestor is not operator-owned"
1255
+ )
1256
+ descriptor = os.open(
1257
+ current,
1258
+ os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0),
1259
+ )
1260
+ try:
1261
+ if _stat_identity(os.fstat(descriptor)) != _stat_identity(info):
1262
+ raise _CoordinatorProtocolError(
1263
+ "coordinator system anchor ancestor changed identity"
1264
+ )
1265
+ finally:
1266
+ os.close(descriptor)
1267
+ info = os.lstat(absolute)
1268
+ if (
1269
+ not stat.S_ISREG(info.st_mode)
1270
+ or info.st_uid != owner_uid
1271
+ or info.st_nlink != 1
1272
+ or stat.S_IMODE(info.st_mode) != _ANCHOR_MODE
1273
+ ):
1274
+ raise _CoordinatorProtocolError("coordinator system anchor metadata is invalid")
1275
+ descriptor = os.open(
1276
+ absolute,
1277
+ os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0),
1278
+ )
1279
+ try:
1280
+ opened = os.fstat(descriptor)
1281
+ if _stat_identity(opened) != _stat_identity(info):
1282
+ raise _CoordinatorProtocolError("coordinator system anchor changed identity")
1283
+ raw = os.read(descriptor, _MAX_MESSAGE_BYTES + 1)
1284
+ if len(raw) > _MAX_MESSAGE_BYTES:
1285
+ raise _CoordinatorProtocolError("coordinator system anchor exceeds the byte limit")
1286
+ finally:
1287
+ os.close(descriptor)
1288
+ try:
1289
+ return parse_canonical_json(raw)
1290
+ except CanonicalJSONError as exc:
1291
+ raise _CoordinatorProtocolError(
1292
+ "coordinator system anchor is not strict canonical JSON"
1293
+ ) from exc
1294
+
1295
+
1296
+ def _stat_identity(info: os.stat_result) -> tuple[int, int, int, int, int, int]:
1297
+ return (
1298
+ info.st_dev,
1299
+ info.st_ino,
1300
+ info.st_uid,
1301
+ stat.S_IFMT(info.st_mode),
1302
+ stat.S_IMODE(info.st_mode),
1303
+ info.st_size,
1304
+ )
1305
+
1306
+
1307
+ __all__ = ["coordinator_main"]