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,1067 @@
1
+ """Content-addressed source authority prepared beside one immutable Recipe Build.
2
+
3
+ The Recipe candidate already seals the authoritative acquisition document. This module projects
4
+ each static source's acquired snapshot, receipt, and observation into content-addressed objects for
5
+ Studio source staging without inventing Studio artifact identifiers. Connector-backed sources bind
6
+ their exact executable adapter coordinate instead. A closed bundle manifest matches Studio's
7
+ reviewed-Build contract and binds the inventory to the candidate, Recipe, and execution.
8
+
9
+ Publication is manifest-last. Object files may exist without granting closure after an interrupted
10
+ write; only the digest-named bundle is a complete result. Every install is create-only and an
11
+ exact repeat converges on the existing bytes. Existing bytes, names, or file kinds that disagree
12
+ fail closed.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ import os
19
+ import re
20
+ import stat
21
+ import uuid
22
+ from collections.abc import Mapping
23
+ from dataclasses import dataclass
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ from mostlyright.data_harness import pipeline, recipe
28
+ from mostlyright.data_harness.canonical import (
29
+ CanonicalJSONError,
30
+ canonical_json_bytes,
31
+ parse_canonical_json,
32
+ )
33
+
34
+ ACQUISITION_BUNDLE_SCHEMA = "mostlyright-deployment-acquisition-bundle.v2"
35
+ PERSISTED_ACQUISITION_BUNDLE_SCHEMA = "mostlyright-persisted-acquisition-bundle.v1"
36
+ ACQUISITION_STORE_RELATIVE = Path("evidence") / "acquisition"
37
+ MAX_OBJECT_BYTES = 4 * 1024 * 1024
38
+ MAX_SOURCE_OBJECT_BYTES = pipeline.MAX_SOURCE_BYTES
39
+ MAX_BUNDLE_BYTES = 4 * 1024 * 1024
40
+ _DIGEST = re.compile(r"^[0-9a-f]{64}$")
41
+ _IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$")
42
+ _SEMVER = re.compile(r"^[1-9][0-9]*\.[0-9]+\.[0-9]+$")
43
+
44
+
45
+ class DeploymentEvidenceError(RuntimeError):
46
+ """The local acquisition-evidence projection is absent, changed, or unsafe."""
47
+
48
+ def __init__(self, code: str, detail: str) -> None:
49
+ self.code = code
50
+ self.detail = detail
51
+ super().__init__(f"{code}: {detail}")
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class PersistedAcquisitionBundle:
56
+ bundle_digest: str
57
+ bundle_path: Path
58
+ candidate_digest: str
59
+ recipe_digest: str
60
+ execution_digest: str
61
+ source_count: int
62
+ object_count: int
63
+
64
+ def to_dict(self) -> dict[str, Any]:
65
+ return {
66
+ "schema_version": PERSISTED_ACQUISITION_BUNDLE_SCHEMA,
67
+ "bundle_digest": self.bundle_digest,
68
+ "bundle_path": str(self.bundle_path),
69
+ "candidate_digest": self.candidate_digest,
70
+ "recipe_digest": self.recipe_digest,
71
+ "execution_digest": self.execution_digest,
72
+ "source_count": self.source_count,
73
+ "object_count": self.object_count,
74
+ }
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class AcquisitionSourcePayload:
79
+ """One exact static source triplet retained from the authenticated local bundle."""
80
+
81
+ source: dict[str, Any]
82
+ source_data: bytes
83
+ acquisition_receipt: bytes
84
+ source_observation: bytes
85
+
86
+
87
+ def persist_acquisition_bundle(
88
+ run_dir: Path,
89
+ *,
90
+ previous_run_dir: Path | None = None,
91
+ ) -> PersistedAcquisitionBundle:
92
+ """Project exact receipt/observation evidence and publish its closed bundle last."""
93
+
94
+ run = _safe_run_dir(run_dir)
95
+ handle = _open_run_handle(run)
96
+ store: _StoreDirectories | None = None
97
+ try:
98
+ store = _store_directories(run, handle.run_fd)
99
+
100
+ def validate_transaction() -> None:
101
+ _validate_run_handle(handle)
102
+ store.validate()
103
+
104
+ with pipeline.open_verified_snapshot(
105
+ run,
106
+ _run_fd=handle.run_fd,
107
+ _ancestor_validator=validate_transaction,
108
+ ) as snapshot:
109
+ inspection, embedded = recipe.verify_recipe_candidate(
110
+ run,
111
+ previous_run_dir=None if previous_run_dir is None else Path(previous_run_dir),
112
+ _snapshot=snapshot,
113
+ )
114
+ acquisition = embedded["acquisition"]
115
+ document = _bundle_document(inspection, embedded, snapshot=snapshot)
116
+ raw_bundle = canonical_json_bytes(document)
117
+ bundle_digest = _sha256(raw_bundle)
118
+
119
+ acquisition_by_id = {source["source_id"]: source for source in acquisition["sources"]}
120
+ execution_by_id = {source.source_id: source for source in embedded["execution"].sources}
121
+ for source in document["sources"]:
122
+ if source["authority_kind"] != "sealed_artifact":
123
+ continue
124
+ source_id = source["source_id"]
125
+ sealed = acquisition_by_id[source_id]
126
+ if execution_by_id[source_id].artifact_input is None:
127
+ raise DeploymentEvidenceError(
128
+ "DEPLOYMENT_EVIDENCE_BINDING",
129
+ "a sealed source bundle entry has no artifact execution authority",
130
+ )
131
+ objects = {
132
+ "source_data": snapshot.member_bytes(f"raw/{source_id}.csv"),
133
+ "acquisition_receipt": canonical_json_bytes(sealed["acquisition_receipt"]),
134
+ "source_observation": canonical_json_bytes(sealed["observation"]),
135
+ }
136
+ for role, raw in objects.items():
137
+ validate_transaction()
138
+ digest = _sha256(raw)
139
+ suffix = ".bin" if role == "source_data" else ".json"
140
+ _install_exact(
141
+ store.objects / f"{digest}{suffix}",
142
+ raw,
143
+ maximum=(
144
+ MAX_SOURCE_OBJECT_BYTES if role == "source_data" else MAX_OBJECT_BYTES
145
+ ),
146
+ directory_fd=store.objects_fd,
147
+ bindings=store.files,
148
+ )
149
+ validate_transaction()
150
+
151
+ bundle_path = store.bundles / f"{bundle_digest}.json"
152
+ validate_transaction()
153
+ _install_exact(
154
+ bundle_path,
155
+ raw_bundle,
156
+ maximum=MAX_BUNDLE_BYTES,
157
+ directory_fd=store.bundles_fd,
158
+ bindings=store.files,
159
+ )
160
+ validate_transaction()
161
+ snapshot.validate()
162
+ validate_transaction()
163
+
164
+ persisted = _parse_acquisition_bundle_bound(
165
+ run,
166
+ bundle_digest,
167
+ handle=handle,
168
+ store=store,
169
+ previous_run_dir=previous_run_dir,
170
+ )
171
+ if persisted.bundle_path != bundle_path:
172
+ raise DeploymentEvidenceError(
173
+ "DEPLOYMENT_EVIDENCE_PATH",
174
+ "the published bundle resolved to a different content-addressed path",
175
+ )
176
+ validate_transaction()
177
+ return persisted
178
+ finally:
179
+ try:
180
+ if store is not None:
181
+ store.close()
182
+ finally:
183
+ handle.close()
184
+
185
+
186
+ def parse_acquisition_bundle(
187
+ run_dir: Path,
188
+ bundle_digest: str,
189
+ *,
190
+ previous_run_dir: Path | None = None,
191
+ ) -> PersistedAcquisitionBundle:
192
+ """Authenticate one published bundle against the exact sealed Recipe candidate."""
193
+
194
+ run = _safe_run_dir(run_dir)
195
+ handle = _open_run_handle(run)
196
+ store: _StoreDirectories | None = None
197
+ try:
198
+ store = _existing_store(run, handle.run_fd)
199
+ return _parse_acquisition_bundle_bound(
200
+ run,
201
+ bundle_digest,
202
+ handle=handle,
203
+ store=store,
204
+ previous_run_dir=previous_run_dir,
205
+ )
206
+ finally:
207
+ try:
208
+ if store is not None:
209
+ store.close()
210
+ finally:
211
+ handle.close()
212
+
213
+
214
+ def load_acquisition_bundle_document(
215
+ run_dir: Path,
216
+ bundle_digest: str,
217
+ *,
218
+ previous_run_dir: Path | None = None,
219
+ ) -> dict[str, Any]:
220
+ """Return the exact canonical bundle after authenticating it to the reviewed Build."""
221
+
222
+ run = _safe_run_dir(run_dir)
223
+ handle = _open_run_handle(run)
224
+ store: _StoreDirectories | None = None
225
+ try:
226
+ store = _existing_store(run, handle.run_fd)
227
+ persisted = _parse_acquisition_bundle_bound(
228
+ run,
229
+ bundle_digest,
230
+ handle=handle,
231
+ store=store,
232
+ previous_run_dir=previous_run_dir,
233
+ )
234
+ raw = _read_exact(
235
+ store.bundles / f"{persisted.bundle_digest}.json",
236
+ maximum=MAX_BUNDLE_BYTES,
237
+ directory_fd=store.bundles_fd,
238
+ bindings=store.files,
239
+ )
240
+ store.validate()
241
+ value = parse_canonical_json(raw)
242
+ if not isinstance(value, dict): # already guaranteed above; keep the public seam typed
243
+ raise DeploymentEvidenceError(
244
+ "DEPLOYMENT_EVIDENCE_FIELDS", "the acquisition bundle must be one object"
245
+ )
246
+ return dict(value)
247
+ finally:
248
+ try:
249
+ if store is not None:
250
+ store.close()
251
+ finally:
252
+ handle.close()
253
+
254
+
255
+ def load_acquisition_source_payload(
256
+ run_dir: Path,
257
+ bundle_digest: str,
258
+ source_id: str,
259
+ *,
260
+ previous_run_dir: Path | None = None,
261
+ ) -> AcquisitionSourcePayload:
262
+ """Return one static source triplet without releasing its bound store during reads."""
263
+
264
+ run = _safe_run_dir(run_dir)
265
+ handle = _open_run_handle(run)
266
+ store: _StoreDirectories | None = None
267
+ try:
268
+ store = _existing_store(run, handle.run_fd)
269
+ persisted = _parse_acquisition_bundle_bound(
270
+ run,
271
+ bundle_digest,
272
+ handle=handle,
273
+ store=store,
274
+ previous_run_dir=previous_run_dir,
275
+ )
276
+ raw_bundle = _read_exact(
277
+ store.bundles / f"{persisted.bundle_digest}.json",
278
+ maximum=MAX_BUNDLE_BYTES,
279
+ directory_fd=store.bundles_fd,
280
+ bindings=store.files,
281
+ )
282
+ value = parse_canonical_json(raw_bundle)
283
+ if not isinstance(value, dict) or not isinstance(value.get("sources"), list):
284
+ raise DeploymentEvidenceError(
285
+ "DEPLOYMENT_EVIDENCE_FIELDS", "the acquisition bundle source inventory is invalid"
286
+ )
287
+ matches = [
288
+ item
289
+ for item in value["sources"]
290
+ if isinstance(item, dict) and item.get("source_id") == source_id
291
+ ]
292
+ if len(matches) != 1 or matches[0].get("authority_kind") != "sealed_artifact":
293
+ raise DeploymentEvidenceError(
294
+ "DEPLOYMENT_EVIDENCE_SOURCE",
295
+ f"the acquisition bundle does not contain one static source named {source_id}",
296
+ )
297
+ source = matches[0]
298
+ objects: dict[str, bytes] = {}
299
+ for role in ("source_data", "acquisition_receipt", "source_observation"):
300
+ reference = source[role]
301
+ digest = reference["content_sha256"]
302
+ suffix = ".bin" if role == "source_data" else ".json"
303
+ raw = _read_exact(
304
+ store.objects / f"{digest}{suffix}",
305
+ maximum=MAX_SOURCE_OBJECT_BYTES if role == "source_data" else MAX_OBJECT_BYTES,
306
+ directory_fd=store.objects_fd,
307
+ bindings=store.files,
308
+ )
309
+ if _sha256(raw) != digest or len(raw) != reference["size_bytes"]:
310
+ raise DeploymentEvidenceError(
311
+ "DEPLOYMENT_EVIDENCE_DIGEST",
312
+ f"the {role} object for {source_id} differs from its closed reference",
313
+ )
314
+ objects[role] = raw
315
+ store.validate()
316
+ return AcquisitionSourcePayload(
317
+ source=dict(source),
318
+ source_data=objects["source_data"],
319
+ acquisition_receipt=objects["acquisition_receipt"],
320
+ source_observation=objects["source_observation"],
321
+ )
322
+ finally:
323
+ try:
324
+ if store is not None:
325
+ store.close()
326
+ finally:
327
+ handle.close()
328
+
329
+
330
+ def _parse_acquisition_bundle_bound(
331
+ run: Path,
332
+ bundle_digest: str,
333
+ *,
334
+ handle: Any,
335
+ store: _StoreDirectories,
336
+ previous_run_dir: Path | None,
337
+ ) -> PersistedAcquisitionBundle:
338
+ digest = _digest(bundle_digest, "bundle digest")
339
+ bundle_path = store.bundles / f"{digest}.json"
340
+ store.validate()
341
+ raw = _read_exact(
342
+ bundle_path,
343
+ maximum=MAX_BUNDLE_BYTES,
344
+ directory_fd=store.bundles_fd,
345
+ bindings=store.files,
346
+ )
347
+ store.validate()
348
+ if _sha256(raw) != digest:
349
+ raise DeploymentEvidenceError(
350
+ "DEPLOYMENT_EVIDENCE_DIGEST",
351
+ "the acquisition bundle bytes do not match their content-addressed name",
352
+ )
353
+ try:
354
+ value = parse_canonical_json(raw)
355
+ except CanonicalJSONError as error:
356
+ raise DeploymentEvidenceError(
357
+ "DEPLOYMENT_EVIDENCE_CANONICAL",
358
+ "the acquisition bundle is not canonical JSON",
359
+ ) from error
360
+ if not isinstance(value, dict):
361
+ raise DeploymentEvidenceError(
362
+ "DEPLOYMENT_EVIDENCE_FIELDS", "the acquisition bundle must be one object"
363
+ )
364
+
365
+ def validate_transaction() -> None:
366
+ _validate_run_handle(handle)
367
+ store.validate()
368
+
369
+ with pipeline.open_verified_snapshot(
370
+ run,
371
+ _run_fd=handle.run_fd,
372
+ _ancestor_validator=validate_transaction,
373
+ ) as snapshot:
374
+ inspection, embedded = recipe.verify_recipe_candidate(
375
+ run,
376
+ previous_run_dir=None if previous_run_dir is None else Path(previous_run_dir),
377
+ _snapshot=snapshot,
378
+ )
379
+ expected = _bundle_document(inspection, embedded, snapshot=snapshot)
380
+ _parse_bundle_shape(value)
381
+ if value != expected:
382
+ raise DeploymentEvidenceError(
383
+ "DEPLOYMENT_EVIDENCE_BINDING",
384
+ "the acquisition bundle does not match the exact sealed candidate evidence",
385
+ )
386
+
387
+ object_digests: set[str] = set()
388
+ expected_sources = {item["source_id"]: item for item in embedded["acquisition"]["sources"]}
389
+ for source in value["sources"]:
390
+ if source["authority_kind"] != "sealed_artifact":
391
+ continue
392
+ sealed = expected_sources[source["source_id"]]
393
+ expected_objects = {
394
+ "source_data": snapshot.member_bytes(f"raw/{source['source_id']}.csv"),
395
+ "acquisition_receipt": canonical_json_bytes(sealed["acquisition_receipt"]),
396
+ "source_observation": canonical_json_bytes(sealed["observation"]),
397
+ }
398
+ for role, expected_object in expected_objects.items():
399
+ validate_transaction()
400
+ reference = source[role]
401
+ object_digest = reference["content_sha256"]
402
+ suffix = ".bin" if role == "source_data" else ".json"
403
+ raw_object = _read_exact(
404
+ store.objects / f"{object_digest}{suffix}",
405
+ maximum=(
406
+ MAX_SOURCE_OBJECT_BYTES if role == "source_data" else MAX_OBJECT_BYTES
407
+ ),
408
+ directory_fd=store.objects_fd,
409
+ bindings=store.files,
410
+ )
411
+ validate_transaction()
412
+ if _sha256(raw_object) != object_digest:
413
+ raise DeploymentEvidenceError(
414
+ "DEPLOYMENT_EVIDENCE_DIGEST",
415
+ f"the {role} object for {source['source_id']} mismatches its digest",
416
+ )
417
+ if raw_object != expected_object:
418
+ raise DeploymentEvidenceError(
419
+ "DEPLOYMENT_EVIDENCE_BINDING",
420
+ f"the {role} object for {source['source_id']} was spliced from "
421
+ "another source",
422
+ )
423
+ object_digests.add(object_digest)
424
+ snapshot.validate()
425
+ validate_transaction()
426
+
427
+ return PersistedAcquisitionBundle(
428
+ bundle_digest=digest,
429
+ bundle_path=bundle_path,
430
+ candidate_digest=value["candidate_digest"],
431
+ recipe_digest=value["recipe_digest"],
432
+ execution_digest=value["execution_digest"],
433
+ source_count=len(value["sources"]),
434
+ object_count=len(object_digests),
435
+ )
436
+
437
+
438
+ @dataclass
439
+ class _FileBinding:
440
+ directory_fd: int
441
+ name: str
442
+ descriptor: int
443
+ identity: tuple[int, int, int, int, int, int, int]
444
+
445
+ def validate(self) -> None:
446
+ try:
447
+ opened = os.fstat(self.descriptor)
448
+ named = os.stat(self.name, dir_fd=self.directory_fd, follow_symlinks=False)
449
+ except OSError as error:
450
+ raise DeploymentEvidenceError(
451
+ "DEPLOYMENT_EVIDENCE_RACE",
452
+ f"evidence object {self.name} changed during evidence handling",
453
+ ) from error
454
+ if (
455
+ not _safe_regular_file(opened)
456
+ or not _safe_regular_file(named)
457
+ or _file_identity(opened) != self.identity
458
+ or _file_identity(named) != self.identity
459
+ ):
460
+ raise DeploymentEvidenceError(
461
+ "DEPLOYMENT_EVIDENCE_RACE",
462
+ f"evidence object {self.name} changed during evidence handling",
463
+ )
464
+
465
+
466
+ @dataclass
467
+ class _DirectoryBinding:
468
+ parent_fd: int
469
+ name: str
470
+ descriptor: int
471
+ device: int
472
+ inode: int
473
+
474
+ @classmethod
475
+ def capture(cls, parent_fd: int, name: str, descriptor: int) -> _DirectoryBinding:
476
+ opened = os.fstat(descriptor)
477
+ return cls(
478
+ parent_fd=parent_fd,
479
+ name=name,
480
+ descriptor=descriptor,
481
+ device=opened.st_dev,
482
+ inode=opened.st_ino,
483
+ )
484
+
485
+ def validate(self) -> None:
486
+ try:
487
+ opened = os.fstat(self.descriptor)
488
+ named = os.stat(self.name, dir_fd=self.parent_fd, follow_symlinks=False)
489
+ except OSError as error:
490
+ raise DeploymentEvidenceError(
491
+ "DEPLOYMENT_EVIDENCE_RACE",
492
+ f"evidence directory {self.name} changed during evidence handling",
493
+ ) from error
494
+ expected = (self.device, self.inode)
495
+ if (
496
+ not stat.S_ISDIR(opened.st_mode)
497
+ or not stat.S_ISDIR(named.st_mode)
498
+ or (opened.st_dev, opened.st_ino) != expected
499
+ or (named.st_dev, named.st_ino) != expected
500
+ ):
501
+ raise DeploymentEvidenceError(
502
+ "DEPLOYMENT_EVIDENCE_RACE",
503
+ f"evidence directory {self.name} changed during evidence handling",
504
+ )
505
+
506
+
507
+ @dataclass
508
+ class _StoreDirectories:
509
+ root: Path
510
+ objects: Path
511
+ bundles: Path
512
+ objects_fd: int
513
+ bundles_fd: int
514
+ descriptors: list[int]
515
+ bindings: list[_DirectoryBinding]
516
+ files: list[_FileBinding]
517
+
518
+ def validate(self) -> None:
519
+ for binding in self.bindings:
520
+ binding.validate()
521
+ for file in self.files:
522
+ file.validate()
523
+
524
+ def close(self) -> None:
525
+ descriptors = list(reversed(self.descriptors))
526
+ self.descriptors.clear()
527
+ file_descriptors = [file.descriptor for file in reversed(self.files)]
528
+ self.files.clear()
529
+ first_error: OSError | None = None
530
+ for descriptor in file_descriptors + descriptors:
531
+ try:
532
+ os.close(descriptor)
533
+ except OSError as error:
534
+ if first_error is None:
535
+ first_error = error
536
+ if first_error is not None:
537
+ raise first_error
538
+
539
+
540
+ def _bundle_document(
541
+ inspection: pipeline.CandidateInspection,
542
+ embedded: Mapping[str, Any],
543
+ *,
544
+ snapshot: pipeline.VerifiedSnapshot,
545
+ ) -> dict[str, Any]:
546
+ acquisition = embedded["acquisition"]
547
+ acquisition_by_id = {source["source_id"]: source for source in acquisition["sources"]}
548
+ recipe_by_id = {source.source_id: source for source in embedded["recipe"].sources}
549
+ execution_by_id = {source.source_id: source for source in embedded["execution"].sources}
550
+ source_ids = sorted(recipe_by_id)
551
+ if (
552
+ set(acquisition_by_id) != set(source_ids)
553
+ or set(execution_by_id) != set(source_ids)
554
+ or len(source_ids) != len(embedded["recipe"].sources)
555
+ ):
556
+ raise DeploymentEvidenceError(
557
+ "DEPLOYMENT_EVIDENCE_BINDING",
558
+ "the Recipe, execution, and acquisition source inventories differ",
559
+ )
560
+ sources: list[dict[str, Any]] = []
561
+ for source_id in source_ids:
562
+ acquired = acquisition_by_id[source_id]
563
+ recipe_source = recipe_by_id[source_id]
564
+ execution_source = execution_by_id[source_id]
565
+ if execution_source.artifact_input is None:
566
+ sources.append(
567
+ {
568
+ "source_id": source_id,
569
+ "authority_kind": "connector",
570
+ "adapter_id": recipe_source.adapter_id,
571
+ "adapter_version": recipe_source.adapter_version,
572
+ }
573
+ )
574
+ continue
575
+ source_raw = snapshot.member_bytes(f"raw/{source_id}.csv")
576
+ receipt_raw = canonical_json_bytes(acquired["acquisition_receipt"])
577
+ observation_raw = canonical_json_bytes(acquired["observation"])
578
+ receipt = acquired["acquisition_receipt"]
579
+ receipt_snapshot = receipt.get("snapshot")
580
+ if receipt_snapshot is None and receipt.get("schema_version") == (
581
+ "recorded-artifact-receipt.v1"
582
+ ):
583
+ # A sealed local artifact carries the flat recorded-artifact receipt the
584
+ # recipe executor writes, not a network acquisition's snapshot record.
585
+ # The binding it offers is the same one this bundle needs — the exact
586
+ # content digest of the sealed raw member — so verify that digest and
587
+ # take the media type from the sealed Recipe source, which is the
588
+ # authority that declared it. Every other shape still fails closed.
589
+ recipe_media_type = recipe_source.query_template.get("media_type")
590
+ if (
591
+ set(receipt) != {"schema_version", "source_id", "content_sha256"}
592
+ or receipt.get("source_id") != source_id
593
+ or receipt.get("content_sha256") != _sha256(source_raw)
594
+ or not isinstance(recipe_media_type, str)
595
+ or not recipe_media_type
596
+ ):
597
+ raise DeploymentEvidenceError(
598
+ "DEPLOYMENT_EVIDENCE_BINDING",
599
+ f"the acquired snapshot for {source_id} differs from its receipt",
600
+ )
601
+ receipt_snapshot = {
602
+ "media_type": recipe_media_type,
603
+ "content_sha256": receipt["content_sha256"],
604
+ "size_bytes": len(source_raw),
605
+ }
606
+ if (
607
+ not isinstance(receipt_snapshot, dict)
608
+ or receipt_snapshot.get("content_sha256") != _sha256(source_raw)
609
+ or receipt_snapshot.get("size_bytes") != len(source_raw)
610
+ or not isinstance(receipt_snapshot.get("media_type"), str)
611
+ ):
612
+ raise DeploymentEvidenceError(
613
+ "DEPLOYMENT_EVIDENCE_BINDING",
614
+ f"the acquired snapshot for {source_id} differs from its receipt",
615
+ )
616
+ sources.append(
617
+ {
618
+ "source_id": source_id,
619
+ "authority_kind": "sealed_artifact",
620
+ "source_data": {
621
+ "media_type": receipt_snapshot["media_type"],
622
+ "content_sha256": _sha256(source_raw),
623
+ "size_bytes": len(source_raw),
624
+ },
625
+ "acquisition_receipt": {
626
+ "media_type": "application/json",
627
+ "content_sha256": _sha256(receipt_raw),
628
+ "size_bytes": len(receipt_raw),
629
+ },
630
+ "source_observation": {
631
+ "media_type": "application/json",
632
+ "content_sha256": _sha256(observation_raw),
633
+ "size_bytes": len(observation_raw),
634
+ },
635
+ }
636
+ )
637
+ return {
638
+ "schema_version": ACQUISITION_BUNDLE_SCHEMA,
639
+ "candidate_digest": inspection.candidate_digest,
640
+ "recipe_digest": embedded["recipe"].digest,
641
+ "execution_digest": embedded["execution"].digest,
642
+ "source_count": len(sources),
643
+ "sources": sources,
644
+ }
645
+
646
+
647
+ def _parse_bundle_shape(value: Mapping[str, Any]) -> None:
648
+ expected = {
649
+ "schema_version",
650
+ "candidate_digest",
651
+ "recipe_digest",
652
+ "execution_digest",
653
+ "source_count",
654
+ "sources",
655
+ }
656
+ if set(value) != expected or value.get("schema_version") != ACQUISITION_BUNDLE_SCHEMA:
657
+ raise DeploymentEvidenceError(
658
+ "DEPLOYMENT_EVIDENCE_FIELDS", "the acquisition bundle fields are not exact"
659
+ )
660
+ for field in (
661
+ "candidate_digest",
662
+ "recipe_digest",
663
+ "execution_digest",
664
+ ):
665
+ _digest(value.get(field), field)
666
+ sources = value.get("sources")
667
+ if not isinstance(sources, list) or not sources:
668
+ raise DeploymentEvidenceError(
669
+ "DEPLOYMENT_EVIDENCE_FIELDS", "the acquisition bundle source inventory is invalid"
670
+ )
671
+ if value.get("source_count") != len(sources):
672
+ raise DeploymentEvidenceError(
673
+ "DEPLOYMENT_EVIDENCE_FIELDS", "the acquisition bundle source count is invalid"
674
+ )
675
+ source_ids: list[str] = []
676
+ for source in sources:
677
+ if not isinstance(source, dict):
678
+ raise DeploymentEvidenceError(
679
+ "DEPLOYMENT_EVIDENCE_FIELDS", "an acquisition bundle source entry is invalid"
680
+ )
681
+ source_id = source.get("source_id")
682
+ if not isinstance(source_id, str) or _IDENTIFIER.fullmatch(source_id) is None:
683
+ raise DeploymentEvidenceError(
684
+ "DEPLOYMENT_EVIDENCE_FIELDS", "an acquisition bundle source id is invalid"
685
+ )
686
+ source_ids.append(source_id)
687
+ authority_kind = source.get("authority_kind")
688
+ if authority_kind == "connector":
689
+ if set(source) != {
690
+ "source_id",
691
+ "authority_kind",
692
+ "adapter_id",
693
+ "adapter_version",
694
+ } or any(
695
+ not isinstance(source.get(field), str) or pattern.fullmatch(source[field]) is None
696
+ for field, pattern in (("adapter_id", _IDENTIFIER), ("adapter_version", _SEMVER))
697
+ ):
698
+ raise DeploymentEvidenceError(
699
+ "DEPLOYMENT_EVIDENCE_FIELDS",
700
+ "a connector acquisition bundle source entry is invalid",
701
+ )
702
+ continue
703
+ if authority_kind != "sealed_artifact" or set(source) != {
704
+ "source_id",
705
+ "authority_kind",
706
+ "source_data",
707
+ "acquisition_receipt",
708
+ "source_observation",
709
+ }:
710
+ raise DeploymentEvidenceError(
711
+ "DEPLOYMENT_EVIDENCE_FIELDS",
712
+ "a static acquisition bundle source entry is invalid",
713
+ )
714
+ for role in ("source_data", "acquisition_receipt", "source_observation"):
715
+ reference = source.get(role)
716
+ if not isinstance(reference, dict) or set(reference) != {
717
+ "media_type",
718
+ "content_sha256",
719
+ "size_bytes",
720
+ }:
721
+ raise DeploymentEvidenceError(
722
+ "DEPLOYMENT_EVIDENCE_FIELDS", f"the {role} object reference is invalid"
723
+ )
724
+ if role != "source_data" and reference.get("media_type") != "application/json":
725
+ raise DeploymentEvidenceError(
726
+ "DEPLOYMENT_EVIDENCE_FIELDS", f"the {role} media type is invalid"
727
+ )
728
+ _digest(reference.get("content_sha256"), f"{role} digest")
729
+ size = reference.get("size_bytes")
730
+ maximum = MAX_SOURCE_OBJECT_BYTES if role == "source_data" else MAX_OBJECT_BYTES
731
+ if type(size) is not int or not 1 <= size <= maximum:
732
+ raise DeploymentEvidenceError(
733
+ "DEPLOYMENT_EVIDENCE_FIELDS", f"the {role} size is invalid"
734
+ )
735
+ if source_ids != sorted(set(source_ids)):
736
+ raise DeploymentEvidenceError(
737
+ "DEPLOYMENT_EVIDENCE_FIELDS", "the acquisition bundle source ids are not sorted unique"
738
+ )
739
+
740
+
741
+ def _safe_run_dir(run_dir: Path) -> Path:
742
+ run = Path(run_dir).absolute()
743
+ if run == Path(run.anchor) or not run.name:
744
+ raise DeploymentEvidenceError(
745
+ "DEPLOYMENT_EVIDENCE_PATH", "the Recipe run path must name one non-root directory"
746
+ )
747
+ return run
748
+
749
+
750
+ def _open_run_handle(run_dir: Path) -> Any:
751
+ try:
752
+ return pipeline._open_candidate_run_handle(run_dir)
753
+ except pipeline.BuildError as error:
754
+ raise DeploymentEvidenceError(
755
+ "DEPLOYMENT_EVIDENCE_PATH", "the Recipe run directory is unavailable or unsafe"
756
+ ) from error
757
+
758
+
759
+ def _validate_run_handle(handle: Any) -> None:
760
+ try:
761
+ pipeline._validate_candidate_run_handle(handle)
762
+ except pipeline.BuildError as error:
763
+ raise DeploymentEvidenceError(
764
+ "DEPLOYMENT_EVIDENCE_RACE", "the Recipe run path changed during evidence handling"
765
+ ) from error
766
+
767
+
768
+ def _store_directories(run_dir: Path, run_fd: int) -> _StoreDirectories:
769
+ root = run_dir / ACQUISITION_STORE_RELATIVE
770
+ objects = root / "objects" / "sha256"
771
+ bundles = root / "bundles" / "sha256"
772
+ descriptors: list[int] = []
773
+ try:
774
+ evidence_fd = _open_directory_at(run_fd, "evidence", create=True)
775
+ descriptors.append(evidence_fd)
776
+ root_fd = _open_directory_at(evidence_fd, "acquisition", create=True)
777
+ descriptors.append(root_fd)
778
+ objects_parent_fd = _open_directory_at(root_fd, "objects", create=True)
779
+ descriptors.append(objects_parent_fd)
780
+ objects_fd = _open_directory_at(objects_parent_fd, "sha256", create=True)
781
+ descriptors.append(objects_fd)
782
+ bundles_parent_fd = _open_directory_at(root_fd, "bundles", create=True)
783
+ descriptors.append(bundles_parent_fd)
784
+ bundles_fd = _open_directory_at(bundles_parent_fd, "sha256", create=True)
785
+ descriptors.append(bundles_fd)
786
+ return _StoreDirectories(
787
+ root=root,
788
+ objects=objects,
789
+ bundles=bundles,
790
+ objects_fd=objects_fd,
791
+ bundles_fd=bundles_fd,
792
+ descriptors=descriptors,
793
+ bindings=_directory_bindings(run_fd, descriptors),
794
+ files=[],
795
+ )
796
+ except BaseException:
797
+ for descriptor in reversed(descriptors):
798
+ os.close(descriptor)
799
+ raise
800
+
801
+
802
+ def _existing_store(run_dir: Path, run_fd: int) -> _StoreDirectories:
803
+ root = run_dir / ACQUISITION_STORE_RELATIVE
804
+ descriptors: list[int] = []
805
+ try:
806
+ evidence_fd = _open_directory_at(run_fd, "evidence", create=False)
807
+ descriptors.append(evidence_fd)
808
+ root_fd = _open_directory_at(evidence_fd, "acquisition", create=False)
809
+ descriptors.append(root_fd)
810
+ objects_parent_fd = _open_directory_at(root_fd, "objects", create=False)
811
+ descriptors.append(objects_parent_fd)
812
+ objects_fd = _open_directory_at(objects_parent_fd, "sha256", create=False)
813
+ descriptors.append(objects_fd)
814
+ bundles_parent_fd = _open_directory_at(root_fd, "bundles", create=False)
815
+ descriptors.append(bundles_parent_fd)
816
+ bundles_fd = _open_directory_at(bundles_parent_fd, "sha256", create=False)
817
+ descriptors.append(bundles_fd)
818
+ return _StoreDirectories(
819
+ root=root,
820
+ objects=root / "objects" / "sha256",
821
+ bundles=root / "bundles" / "sha256",
822
+ objects_fd=objects_fd,
823
+ bundles_fd=bundles_fd,
824
+ descriptors=descriptors,
825
+ bindings=_directory_bindings(run_fd, descriptors),
826
+ files=[],
827
+ )
828
+ except BaseException:
829
+ for descriptor in reversed(descriptors):
830
+ os.close(descriptor)
831
+ raise
832
+
833
+
834
+ def _open_directory_at(parent_fd: int, name: str, *, create: bool) -> int:
835
+ flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0)
836
+ if create:
837
+ try:
838
+ os.mkdir(name, mode=0o700, dir_fd=parent_fd)
839
+ except FileExistsError:
840
+ pass
841
+ except OSError as error:
842
+ raise DeploymentEvidenceError(
843
+ "DEPLOYMENT_EVIDENCE_PATH", f"could not create evidence directory {name}"
844
+ ) from error
845
+ else:
846
+ os.fsync(parent_fd)
847
+ try:
848
+ descriptor = os.open(name, flags, dir_fd=parent_fd)
849
+ except OSError as error:
850
+ raise DeploymentEvidenceError(
851
+ "DEPLOYMENT_EVIDENCE_PATH", f"evidence directory {name} is unavailable or unsafe"
852
+ ) from error
853
+ opened = os.fstat(descriptor)
854
+ if not stat.S_ISDIR(opened.st_mode):
855
+ os.close(descriptor)
856
+ raise DeploymentEvidenceError(
857
+ "DEPLOYMENT_EVIDENCE_PATH", f"evidence path {name} is not a real directory"
858
+ )
859
+ return descriptor
860
+
861
+
862
+ def _directory_bindings(run_fd: int, descriptors: list[int]) -> list[_DirectoryBinding]:
863
+ evidence_fd, root_fd, objects_parent_fd, objects_fd, bundles_parent_fd, bundles_fd = descriptors
864
+ return [
865
+ _DirectoryBinding.capture(run_fd, "evidence", evidence_fd),
866
+ _DirectoryBinding.capture(evidence_fd, "acquisition", root_fd),
867
+ _DirectoryBinding.capture(root_fd, "objects", objects_parent_fd),
868
+ _DirectoryBinding.capture(objects_parent_fd, "sha256", objects_fd),
869
+ _DirectoryBinding.capture(root_fd, "bundles", bundles_parent_fd),
870
+ _DirectoryBinding.capture(bundles_parent_fd, "sha256", bundles_fd),
871
+ ]
872
+
873
+
874
+ def _install_exact(
875
+ path: Path,
876
+ raw: bytes,
877
+ *,
878
+ maximum: int,
879
+ directory_fd: int,
880
+ bindings: list[_FileBinding],
881
+ ) -> bool:
882
+ if not 1 <= len(raw) <= maximum:
883
+ raise DeploymentEvidenceError(
884
+ "DEPLOYMENT_EVIDENCE_SIZE", f"evidence object {path.name} exceeds its byte limit"
885
+ )
886
+ try:
887
+ existing = _read_exact(
888
+ path,
889
+ maximum=maximum,
890
+ directory_fd=directory_fd,
891
+ bindings=bindings,
892
+ )
893
+ except DeploymentEvidenceError as error:
894
+ if error.code != "DEPLOYMENT_EVIDENCE_PATH":
895
+ raise
896
+ else:
897
+ if existing != raw:
898
+ raise DeploymentEvidenceError(
899
+ "DEPLOYMENT_EVIDENCE_CONFLICT",
900
+ f"existing content-addressed evidence {path.name} has different bytes",
901
+ )
902
+ return False
903
+ temporary = f".{path.name}.tmp-{uuid.uuid4().hex}"
904
+ flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0)
905
+ descriptor = -1
906
+ try:
907
+ descriptor = os.open(temporary, flags, 0o600, dir_fd=directory_fd)
908
+ offset = 0
909
+ while offset < len(raw):
910
+ offset += os.write(descriptor, raw[offset:])
911
+ os.fsync(descriptor)
912
+ os.close(descriptor)
913
+ descriptor = -1
914
+ try:
915
+ os.link(
916
+ temporary,
917
+ path.name,
918
+ src_dir_fd=directory_fd,
919
+ dst_dir_fd=directory_fd,
920
+ follow_symlinks=False,
921
+ )
922
+ installed = True
923
+ except FileExistsError:
924
+ installed = False
925
+ if installed:
926
+ os.unlink(temporary, dir_fd=directory_fd)
927
+ if (
928
+ _read_exact(
929
+ path,
930
+ maximum=maximum,
931
+ directory_fd=directory_fd,
932
+ bindings=bindings,
933
+ )
934
+ != raw
935
+ ):
936
+ raise DeploymentEvidenceError(
937
+ "DEPLOYMENT_EVIDENCE_CONFLICT",
938
+ f"content-addressed evidence {path.name} did not converge on exact bytes",
939
+ )
940
+ os.fsync(directory_fd)
941
+ return installed
942
+ except DeploymentEvidenceError:
943
+ raise
944
+ except OSError as error:
945
+ raise DeploymentEvidenceError(
946
+ "DEPLOYMENT_EVIDENCE_WRITE", f"could not publish evidence object {path.name}"
947
+ ) from error
948
+ finally:
949
+ if descriptor >= 0:
950
+ os.close(descriptor)
951
+ try:
952
+ os.unlink(temporary, dir_fd=directory_fd)
953
+ except FileNotFoundError:
954
+ pass
955
+
956
+
957
+ def _read_exact(
958
+ path: Path,
959
+ *,
960
+ maximum: int,
961
+ directory_fd: int,
962
+ bindings: list[_FileBinding],
963
+ ) -> bytes:
964
+ flags = os.O_RDONLY | getattr(os, "O_NONBLOCK", 0) | getattr(os, "O_NOFOLLOW", 0)
965
+ try:
966
+ descriptor = os.open(path.name, flags, dir_fd=directory_fd)
967
+ except OSError as error:
968
+ raise DeploymentEvidenceError(
969
+ "DEPLOYMENT_EVIDENCE_PATH", f"evidence object {path.name} is unavailable"
970
+ ) from error
971
+ try:
972
+ before = os.fstat(descriptor)
973
+ named_before = _stat_named_file(path.name, directory_fd)
974
+ identity = _file_identity(before)
975
+ if (
976
+ not _safe_regular_file(before)
977
+ or not _safe_regular_file(named_before)
978
+ or _file_identity(named_before) != identity
979
+ or before.st_size > maximum
980
+ ):
981
+ raise DeploymentEvidenceError(
982
+ "DEPLOYMENT_EVIDENCE_PATH", f"evidence object {path.name} is not a safe file"
983
+ )
984
+ raw = bytearray()
985
+ while len(raw) <= maximum:
986
+ chunk = os.read(descriptor, min(64 * 1024, maximum + 1 - len(raw)))
987
+ if not chunk:
988
+ break
989
+ raw.extend(chunk)
990
+ after = os.fstat(descriptor)
991
+ named_after = _stat_named_file(path.name, directory_fd)
992
+ if (
993
+ len(raw) > maximum
994
+ or not _safe_regular_file(after)
995
+ or not _safe_regular_file(named_after)
996
+ or _file_identity(after) != identity
997
+ or _file_identity(named_after) != identity
998
+ or len(raw) != before.st_size
999
+ ):
1000
+ raise DeploymentEvidenceError(
1001
+ "DEPLOYMENT_EVIDENCE_RACE", f"evidence object {path.name} changed while read"
1002
+ )
1003
+ bindings.append(
1004
+ _FileBinding(
1005
+ directory_fd=directory_fd,
1006
+ name=path.name,
1007
+ descriptor=descriptor,
1008
+ identity=identity,
1009
+ )
1010
+ )
1011
+ descriptor = -1
1012
+ return bytes(raw)
1013
+ finally:
1014
+ if descriptor >= 0:
1015
+ os.close(descriptor)
1016
+
1017
+
1018
+ def _safe_regular_file(value: os.stat_result) -> bool:
1019
+ return stat.S_ISREG(value.st_mode) and value.st_nlink == 1
1020
+
1021
+
1022
+ def _stat_named_file(name: str, directory_fd: int) -> os.stat_result:
1023
+ try:
1024
+ return os.stat(name, dir_fd=directory_fd, follow_symlinks=False)
1025
+ except OSError as error:
1026
+ raise DeploymentEvidenceError(
1027
+ "DEPLOYMENT_EVIDENCE_RACE",
1028
+ f"evidence object {name} changed during evidence handling",
1029
+ ) from error
1030
+
1031
+
1032
+ def _file_identity(value: os.stat_result) -> tuple[int, int, int, int, int, int, int]:
1033
+ return (
1034
+ value.st_dev,
1035
+ value.st_ino,
1036
+ value.st_mode,
1037
+ value.st_nlink,
1038
+ value.st_size,
1039
+ value.st_mtime_ns,
1040
+ value.st_ctime_ns,
1041
+ )
1042
+
1043
+
1044
+ def _digest(value: Any, label: str) -> str:
1045
+ if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
1046
+ raise DeploymentEvidenceError(
1047
+ "DEPLOYMENT_EVIDENCE_FIELDS", f"the {label} is not a lowercase SHA-256 digest"
1048
+ )
1049
+ return value
1050
+
1051
+
1052
+ def _sha256(raw: bytes) -> str:
1053
+ return hashlib.sha256(raw).hexdigest()
1054
+
1055
+
1056
+ __all__ = [
1057
+ "ACQUISITION_BUNDLE_SCHEMA",
1058
+ "ACQUISITION_STORE_RELATIVE",
1059
+ "PERSISTED_ACQUISITION_BUNDLE_SCHEMA",
1060
+ "AcquisitionSourcePayload",
1061
+ "DeploymentEvidenceError",
1062
+ "PersistedAcquisitionBundle",
1063
+ "load_acquisition_bundle_document",
1064
+ "load_acquisition_source_payload",
1065
+ "parse_acquisition_bundle",
1066
+ "persist_acquisition_bundle",
1067
+ ]