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,1037 @@
1
+ """A sealed-catalog query whose answer carries its complete ranking and facts trace."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ import stat
8
+ import tempfile
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from mostlyright.data_harness.canonical import (
14
+ canonical_json_bytes,
15
+ canonical_sha256,
16
+ sha256_bytes,
17
+ )
18
+ from mostlyright.data_harness.sources.catalog.contracts import EMBEDDING_LAYERS
19
+ from mostlyright.data_harness.sources.catalog.embedding import EmbeddingBackend
20
+ from mostlyright.data_harness.sources.catalog.entry_v2 import (
21
+ CatalogEntryV2,
22
+ CatalogEntryV2Decision,
23
+ CatalogFactFilter,
24
+ evaluate_catalog_entry_v2,
25
+ )
26
+ from mostlyright.data_harness.sources.catalog.gating import CatalogRequirement, gate_entries
27
+ from mostlyright.data_harness.sources.catalog.packed_retrieval import (
28
+ PACKED_INCOMPLETE_CODES,
29
+ CatalogSearchWorkLimits,
30
+ PackedSearchWork,
31
+ rank_packed_entries,
32
+ )
33
+ from mostlyright.data_harness.sources.catalog.recommend import (
34
+ RECOMMENDATION_BUCKETS,
35
+ SourceRecommendation,
36
+ recommend_sources,
37
+ )
38
+ from mostlyright.data_harness.sources.catalog.retrieval import _rank_retrieval
39
+ from mostlyright.data_harness.sources.catalog.retrieval_manifest import (
40
+ LEXICAL_FALLBACK_REASONS,
41
+ LoadedRetrievalCatalog,
42
+ load_retrieval_catalog,
43
+ )
44
+ from mostlyright.data_harness.sources.catalog.sealed import CATALOG_FILENAME, SealedEmbeddingIndex
45
+ from mostlyright.data_harness.sources.contracts import SourceContractError
46
+
47
+ CATALOG_QUERY_TRACE_VERSION = "harness-catalog-query-trace.v1"
48
+ CATALOG_QUERY_TRACE_V2_VERSION = "harness-catalog-query-trace.v2"
49
+ CATALOG_PACKED_QUERY_TRACE_VERSION = "harness-catalog-packed-query-trace.v1"
50
+ _DIGEST = re.compile(r"^[0-9a-f]{64}$")
51
+ _MAX_SNAPSHOT_BYTES = 128 * 1024 * 1024
52
+ _MAX_SNAPSHOT_FILES = 40_002
53
+ _OPEN_DIRECTORY = os.O_RDONLY | os.O_DIRECTORY | getattr(os, "O_CLOEXEC", 0)
54
+ _NOFOLLOW = getattr(os, "O_NOFOLLOW", 0)
55
+
56
+
57
+ def _read_plain_file(parent_fd: int, name: str, *, remaining: int) -> bytes:
58
+ """Read one stable, single-link regular file relative to a retained directory."""
59
+
60
+ try:
61
+ descriptor = os.open(
62
+ name,
63
+ os.O_RDONLY | _NOFOLLOW | getattr(os, "O_CLOEXEC", 0),
64
+ dir_fd=parent_fd,
65
+ )
66
+ except OSError as error:
67
+ raise SourceContractError(
68
+ "CATALOG_SNAPSHOT_MEMBER",
69
+ name,
70
+ "cannot open a plain catalog member",
71
+ ) from error
72
+ try:
73
+ before = os.fstat(descriptor)
74
+ if not stat.S_ISREG(before.st_mode) or before.st_nlink != 1:
75
+ raise SourceContractError(
76
+ "CATALOG_SNAPSHOT_MEMBER",
77
+ name,
78
+ "catalog members must be single-link regular files",
79
+ )
80
+ if before.st_size > remaining:
81
+ raise SourceContractError(
82
+ "CATALOG_SNAPSHOT_LIMIT",
83
+ name,
84
+ "catalog snapshot exceeds the byte limit",
85
+ )
86
+ chunks: list[bytes] = []
87
+ observed = 0
88
+ while True:
89
+ chunk = os.read(descriptor, min(1024 * 1024, remaining - observed + 1))
90
+ if not chunk:
91
+ break
92
+ observed += len(chunk)
93
+ if observed > remaining:
94
+ raise SourceContractError(
95
+ "CATALOG_SNAPSHOT_LIMIT",
96
+ name,
97
+ "catalog snapshot exceeds the byte limit",
98
+ )
99
+ chunks.append(chunk)
100
+ after = os.fstat(descriptor)
101
+
102
+ def identity(value: os.stat_result) -> tuple[int, int, int, int, int]:
103
+ return (
104
+ value.st_dev,
105
+ value.st_ino,
106
+ value.st_size,
107
+ value.st_mtime_ns,
108
+ value.st_ctime_ns,
109
+ )
110
+
111
+ content = b"".join(chunks)
112
+ if identity(before) != identity(after) or len(content) != after.st_size:
113
+ raise SourceContractError(
114
+ "CATALOG_SNAPSHOT_RACE",
115
+ name,
116
+ "catalog member changed while captured",
117
+ )
118
+ return content
119
+ finally:
120
+ os.close(descriptor)
121
+
122
+
123
+ def _capture_catalog(directory: Path) -> tuple[bytes, tuple[tuple[str, bytes], ...]]:
124
+ """Capture the catalog from one retained root identity without pathname reopens."""
125
+
126
+ try:
127
+ root_fd = os.open(directory, _OPEN_DIRECTORY | _NOFOLLOW)
128
+ except OSError as error:
129
+ raise SourceContractError(
130
+ "CATALOG_SNAPSHOT_ROOT",
131
+ str(directory),
132
+ "cannot open a plain catalog directory",
133
+ ) from error
134
+ try:
135
+ catalog = _read_plain_file(root_fd, CATALOG_FILENAME, remaining=_MAX_SNAPSHOT_BYTES)
136
+ remaining = _MAX_SNAPSHOT_BYTES - len(catalog)
137
+ try:
138
+ vectors_fd = os.open("vectors", _OPEN_DIRECTORY | _NOFOLLOW, dir_fd=root_fd)
139
+ except OSError as error:
140
+ raise SourceContractError(
141
+ "CATALOG_SNAPSHOT_MEMBER",
142
+ "vectors",
143
+ "cannot open the plain vector directory",
144
+ ) from error
145
+ try:
146
+ entries = sorted(os.scandir(vectors_fd), key=lambda item: item.name)
147
+ if len(entries) > _MAX_SNAPSHOT_FILES - 1:
148
+ raise SourceContractError(
149
+ "CATALOG_SNAPSHOT_LIMIT",
150
+ "vectors",
151
+ "catalog snapshot contains too many members",
152
+ )
153
+ vectors: list[tuple[str, bytes]] = []
154
+ for entry in entries:
155
+ if not re.fullmatch(r"[0-9a-f]{64}\.vec", entry.name):
156
+ raise SourceContractError(
157
+ "CATALOG_SNAPSHOT_MEMBER",
158
+ entry.name,
159
+ "vector member name is not canonical",
160
+ )
161
+ content = _read_plain_file(vectors_fd, entry.name, remaining=remaining)
162
+ remaining -= len(content)
163
+ vectors.append((entry.name, content))
164
+ finally:
165
+ os.close(vectors_fd)
166
+ root_after = os.fstat(root_fd)
167
+ if not stat.S_ISDIR(root_after.st_mode):
168
+ raise SourceContractError(
169
+ "CATALOG_SNAPSHOT_ROOT",
170
+ str(directory),
171
+ "catalog root changed while captured",
172
+ )
173
+ return catalog, tuple(vectors)
174
+ finally:
175
+ os.close(root_fd)
176
+
177
+
178
+ def _load_captured_catalog(
179
+ catalog: bytes, vectors: tuple[tuple[str, bytes], ...]
180
+ ) -> SealedEmbeddingIndex:
181
+ """Load verified bytes from a private snapshot, never from the caller's live path."""
182
+
183
+ with tempfile.TemporaryDirectory(prefix="mostlyright-catalog-snapshot-") as temporary:
184
+ root = Path(temporary)
185
+ vector_root = root / "vectors"
186
+ vector_root.mkdir(mode=0o700)
187
+ (root / CATALOG_FILENAME).write_bytes(catalog)
188
+ for name, content in vectors:
189
+ (vector_root / name).write_bytes(content)
190
+ return SealedEmbeddingIndex.load(root)
191
+
192
+
193
+ @dataclass(frozen=True)
194
+ class TracedCandidate:
195
+ """One ranked candidate with the exact catalog facts the answer exposes."""
196
+
197
+ rank: int
198
+ entry_coordinate: str
199
+ entry_digest: str
200
+ facts_digest: str
201
+ disposition: str
202
+ publisher: str
203
+ title: str
204
+ data_formats: tuple[str, ...]
205
+ update_frequency_seconds: int
206
+ access_kind: str
207
+ authentication_required: bool
208
+
209
+ def to_dict(self) -> dict[str, Any]:
210
+ return {
211
+ "rank": self.rank,
212
+ "entry_coordinate": self.entry_coordinate,
213
+ "entry_digest": self.entry_digest,
214
+ "facts_digest": self.facts_digest,
215
+ "disposition": self.disposition,
216
+ "publisher": self.publisher,
217
+ "title": self.title,
218
+ "data_formats": list(self.data_formats),
219
+ "update_frequency_seconds": self.update_frequency_seconds,
220
+ "access_kind": self.access_kind,
221
+ "authentication_required": self.authentication_required,
222
+ }
223
+
224
+
225
+ @dataclass(frozen=True)
226
+ class CatalogQueryTrace:
227
+ """Canonical catalog-query evidence that callers may seal and replay."""
228
+
229
+ catalog_manifest_sha256: str
230
+ backend_coordinate: str
231
+ question_digest: str
232
+ requirement_digest: str
233
+ candidates: tuple[TracedCandidate, ...]
234
+ admitted: tuple[str, ...]
235
+ escalated: tuple[str, ...]
236
+ refused: tuple[str, ...]
237
+ schema_version: str = CATALOG_QUERY_TRACE_VERSION
238
+
239
+ def __post_init__(self) -> None:
240
+ if self.schema_version != CATALOG_QUERY_TRACE_VERSION:
241
+ raise SourceContractError(
242
+ "VERSION",
243
+ "trace.schema_version",
244
+ "unsupported trace version",
245
+ )
246
+ for name in ("catalog_manifest_sha256", "question_digest", "requirement_digest"):
247
+ value = getattr(self, name)
248
+ if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
249
+ raise SourceContractError("SHA256", f"trace.{name}", "must be a lowercase SHA-256")
250
+ if not isinstance(self.backend_coordinate, str) or "@" not in self.backend_coordinate:
251
+ raise SourceContractError(
252
+ "CATALOG_BACKEND",
253
+ "trace.backend_coordinate",
254
+ "must name the exact query backend version",
255
+ )
256
+ expected_ranks = tuple(range(1, len(self.candidates) + 1))
257
+ if tuple(item.rank for item in self.candidates) != expected_ranks:
258
+ raise SourceContractError(
259
+ "CATALOG_TRACE_RANK",
260
+ "trace.candidates",
261
+ "ranks must be contiguous and ordered from one",
262
+ )
263
+
264
+ @property
265
+ def digest(self) -> str:
266
+ return canonical_sha256(self.to_dict())
267
+
268
+ def to_dict(self) -> dict[str, Any]:
269
+ return {
270
+ "schema_version": self.schema_version,
271
+ "catalog_manifest_sha256": self.catalog_manifest_sha256,
272
+ "backend_coordinate": self.backend_coordinate,
273
+ "question_digest": self.question_digest,
274
+ "requirement_digest": self.requirement_digest,
275
+ "candidates": [item.to_dict() for item in self.candidates],
276
+ "admitted": list(self.admitted),
277
+ "escalated": list(self.escalated),
278
+ "refused": list(self.refused),
279
+ }
280
+
281
+
282
+ @dataclass(frozen=True)
283
+ class CatalogQueryTraceV2:
284
+ """Canonical v2 evidence for one explicitly selected verified representation."""
285
+
286
+ retrieval_manifest_sha256: str
287
+ representation: str
288
+ representation_artifact_sha256: str
289
+ backend_coordinate: str
290
+ fallback_reason: str | None
291
+ requested_backend: str
292
+ lexical_fallback_allowed: bool
293
+ question_digest: str
294
+ requirement_digest: str
295
+ candidates: tuple[TracedCandidate, ...]
296
+ admitted: tuple[str, ...]
297
+ escalated: tuple[str, ...]
298
+ refused: tuple[str, ...]
299
+ schema_version: str = CATALOG_QUERY_TRACE_V2_VERSION
300
+
301
+ def __post_init__(self) -> None:
302
+ if self.schema_version != CATALOG_QUERY_TRACE_V2_VERSION:
303
+ raise SourceContractError(
304
+ "VERSION", "trace.schema_version", "unsupported v2 trace version"
305
+ )
306
+ for name in (
307
+ "retrieval_manifest_sha256",
308
+ "representation_artifact_sha256",
309
+ "question_digest",
310
+ "requirement_digest",
311
+ ):
312
+ value = getattr(self, name)
313
+ if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
314
+ raise SourceContractError("SHA256", f"trace.{name}", "must be a lowercase SHA-256")
315
+ if not isinstance(self.representation, str) or self.representation not in {
316
+ "lexical",
317
+ "neural",
318
+ }:
319
+ raise SourceContractError(
320
+ "RETRIEVAL_BACKEND", "trace.representation", "must be lexical or neural"
321
+ )
322
+ if not isinstance(self.requested_backend, str) or self.requested_backend not in {
323
+ "lexical",
324
+ "neural",
325
+ }:
326
+ raise SourceContractError(
327
+ "RETRIEVAL_BACKEND",
328
+ "trace.requested_backend",
329
+ "must be lexical or neural",
330
+ )
331
+ if type(self.lexical_fallback_allowed) is not bool:
332
+ raise SourceContractError(
333
+ "RETRIEVAL_FALLBACK",
334
+ "trace.lexical_fallback_allowed",
335
+ "must be a boolean",
336
+ )
337
+ if not isinstance(self.backend_coordinate, str) or "@" not in self.backend_coordinate:
338
+ raise SourceContractError(
339
+ "CATALOG_BACKEND",
340
+ "trace.backend_coordinate",
341
+ "must name the exact query backend version",
342
+ )
343
+ if self.fallback_reason is not None and (
344
+ not isinstance(self.fallback_reason, str)
345
+ or self.fallback_reason not in LEXICAL_FALLBACK_REASONS
346
+ ):
347
+ raise SourceContractError(
348
+ "RETRIEVAL_FALLBACK",
349
+ "trace.fallback_reason",
350
+ "must be a governed lexical fallback code",
351
+ )
352
+ if self.requested_backend == "lexical":
353
+ if self.representation != "lexical":
354
+ raise SourceContractError(
355
+ "RETRIEVAL_BACKEND",
356
+ "trace.representation",
357
+ "a lexical request cannot produce a neural representation",
358
+ )
359
+ if self.fallback_reason is not None:
360
+ raise SourceContractError(
361
+ "RETRIEVAL_FALLBACK",
362
+ "trace.fallback_reason",
363
+ "an explicit lexical request did not fall back from neural",
364
+ )
365
+ elif self.representation == "neural":
366
+ if self.fallback_reason is not None:
367
+ raise SourceContractError(
368
+ "RETRIEVAL_FALLBACK",
369
+ "trace.fallback_reason",
370
+ "a neural result cannot also claim lexical fallback",
371
+ )
372
+ elif not self.lexical_fallback_allowed or self.fallback_reason is None:
373
+ raise SourceContractError(
374
+ "RETRIEVAL_FALLBACK",
375
+ "trace.fallback_reason",
376
+ "a neural request may produce lexical only with authorized typed fallback",
377
+ )
378
+ if self.representation == "lexical" and self.backend_coordinate != "lexical_hashing@1.0.0":
379
+ raise SourceContractError(
380
+ "RETRIEVAL_BACKEND",
381
+ "trace.backend_coordinate",
382
+ "a lexical result must name the frozen lexical backend coordinate",
383
+ )
384
+ if self.representation == "neural" and (
385
+ "#" not in self.backend_coordinate or ":" not in self.backend_coordinate
386
+ ):
387
+ raise SourceContractError(
388
+ "RETRIEVAL_BACKEND",
389
+ "trace.backend_coordinate",
390
+ "a neural result must name an explicit tuple-qualified backend coordinate",
391
+ )
392
+
393
+ if not isinstance(self.candidates, tuple) or any(
394
+ type(item) is not TracedCandidate for item in self.candidates
395
+ ):
396
+ raise SourceContractError(
397
+ "CATALOG_TRACE_CANDIDATES",
398
+ "trace.candidates",
399
+ "must be an immutable tuple of strict traced candidates",
400
+ )
401
+ expected_ranks = tuple(range(1, len(self.candidates) + 1))
402
+ if tuple(item.rank for item in self.candidates) != expected_ranks:
403
+ raise SourceContractError(
404
+ "CATALOG_TRACE_RANK",
405
+ "trace.candidates",
406
+ "ranks must be contiguous and ordered from one",
407
+ )
408
+ coordinates: list[str] = []
409
+ dispositions = {disposition for _field, disposition in RECOMMENDATION_BUCKETS}
410
+ for position, item in enumerate(self.candidates):
411
+ if (
412
+ type(item.rank) is not int
413
+ or not isinstance(item.entry_coordinate, str)
414
+ or "@" not in item.entry_coordinate
415
+ or item.entry_coordinate in coordinates
416
+ ):
417
+ raise SourceContractError(
418
+ "CATALOG_TRACE_CANDIDATES",
419
+ f"trace.candidates[{position}].entry_coordinate",
420
+ "must be a unique versioned identity",
421
+ )
422
+ coordinates.append(item.entry_coordinate)
423
+ for name in ("entry_digest", "facts_digest"):
424
+ value = getattr(item, name)
425
+ if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
426
+ raise SourceContractError(
427
+ "CATALOG_TRACE_CANDIDATES",
428
+ f"trace.candidates[{position}].{name}",
429
+ "must be a lowercase SHA-256",
430
+ )
431
+ if not isinstance(item.disposition, str) or item.disposition not in dispositions:
432
+ raise SourceContractError(
433
+ "CATALOG_TRACE_CANDIDATES",
434
+ f"trace.candidates[{position}].disposition",
435
+ "must be a governed facts disposition",
436
+ )
437
+ if (
438
+ not isinstance(item.publisher, str)
439
+ or not item.publisher
440
+ or not isinstance(item.title, str)
441
+ or not item.title
442
+ or not isinstance(item.data_formats, tuple)
443
+ or not item.data_formats
444
+ or any(not isinstance(value, str) or not value for value in item.data_formats)
445
+ or type(item.update_frequency_seconds) is not int
446
+ or item.update_frequency_seconds < 0
447
+ or not isinstance(item.access_kind, str)
448
+ or not item.access_kind
449
+ or type(item.authentication_required) is not bool
450
+ ):
451
+ raise SourceContractError(
452
+ "CATALOG_TRACE_CANDIDATES",
453
+ f"trace.candidates[{position}]",
454
+ "candidate facts must retain their strict immutable types",
455
+ )
456
+ for field, disposition in RECOMMENDATION_BUCKETS:
457
+ observed = getattr(self, field)
458
+ if not isinstance(observed, tuple) or any(type(item) is not str for item in observed):
459
+ raise SourceContractError(
460
+ "CATALOG_TRACE_PARTITION",
461
+ f"trace.{field}",
462
+ "must be an immutable tuple of candidate identities",
463
+ )
464
+ expected = tuple(
465
+ item.entry_coordinate for item in self.candidates if item.disposition == disposition
466
+ )
467
+ if observed != expected:
468
+ raise SourceContractError(
469
+ "CATALOG_TRACE_PARTITION",
470
+ f"trace.{field}",
471
+ "must exactly preserve the ordered candidates with this disposition",
472
+ )
473
+
474
+ @property
475
+ def canonical_bytes(self) -> bytes:
476
+ return canonical_json_bytes(self.to_dict())
477
+
478
+ @property
479
+ def digest(self) -> str:
480
+ return sha256_bytes(self.canonical_bytes)
481
+
482
+ def to_dict(self) -> dict[str, Any]:
483
+ return {
484
+ "schema_version": self.schema_version,
485
+ "retrieval_manifest_sha256": self.retrieval_manifest_sha256,
486
+ "representation": self.representation,
487
+ "representation_artifact_sha256": self.representation_artifact_sha256,
488
+ "backend_coordinate": self.backend_coordinate,
489
+ "fallback_reason": self.fallback_reason,
490
+ "requested_backend": self.requested_backend,
491
+ "lexical_fallback_allowed": self.lexical_fallback_allowed,
492
+ "question_digest": self.question_digest,
493
+ "requirement_digest": self.requirement_digest,
494
+ "candidates": [item.to_dict() for item in self.candidates],
495
+ "admitted": list(self.admitted),
496
+ "escalated": list(self.escalated),
497
+ "refused": list(self.refused),
498
+ }
499
+
500
+
501
+ @dataclass(frozen=True)
502
+ class CatalogQuerySnapshot:
503
+ """One verified in-memory catalog snapshot and the answer derived from those exact bytes."""
504
+
505
+ recommendation: SourceRecommendation
506
+ trace: CatalogQueryTrace
507
+ index: SealedEmbeddingIndex
508
+ catalog_bytes: bytes
509
+ vector_members: tuple[tuple[str, bytes], ...]
510
+
511
+
512
+ def query_catalog_snapshot(
513
+ *,
514
+ directory: Path,
515
+ expected_catalog_manifest_sha256: str,
516
+ question: str,
517
+ requirement: CatalogRequirement,
518
+ limit: int = 5,
519
+ ) -> CatalogQuerySnapshot:
520
+ """Verify and capture a sealed catalog, then answer only from that retained snapshot."""
521
+
522
+ if not isinstance(expected_catalog_manifest_sha256, str) or (
523
+ _DIGEST.fullmatch(expected_catalog_manifest_sha256) is None
524
+ ):
525
+ raise SourceContractError(
526
+ "SHA256",
527
+ "expected_catalog_manifest_sha256",
528
+ "must be a lowercase SHA-256",
529
+ )
530
+ if not isinstance(directory, Path):
531
+ raise SourceContractError("TYPE", "directory", "must be a Path")
532
+ index_path = directory / CATALOG_FILENAME
533
+ manifest_bytes, vector_members = _capture_catalog(directory)
534
+ observed_manifest_sha256 = sha256_bytes(manifest_bytes)
535
+ if observed_manifest_sha256 != expected_catalog_manifest_sha256:
536
+ raise SourceContractError(
537
+ "CATALOG_MANIFEST_MISMATCH",
538
+ str(index_path),
539
+ "sealed catalog index differs from the expected manifest digest",
540
+ )
541
+
542
+ index = _load_captured_catalog(manifest_bytes, vector_members)
543
+ recommendation = recommend_sources(
544
+ index=index,
545
+ question=question,
546
+ requirement=requirement,
547
+ limit=limit,
548
+ )
549
+ by_coordinate = {entry.coordinate: entry for entry in index.entries}
550
+ candidates: list[TracedCandidate] = []
551
+ for rank, verdict in enumerate(recommendation.candidates, start=1):
552
+ entry = by_coordinate[verdict.entry_coordinate]
553
+ candidates.append(
554
+ TracedCandidate(
555
+ rank=rank,
556
+ entry_coordinate=entry.coordinate,
557
+ entry_digest=entry.digest,
558
+ facts_digest=entry.facts_digest,
559
+ disposition=verdict.disposition,
560
+ publisher=entry.publisher,
561
+ title=entry.title,
562
+ data_formats=entry.data_formats,
563
+ update_frequency_seconds=entry.observation.update_frequency_seconds,
564
+ access_kind=entry.access_kind,
565
+ authentication_required=entry.authentication_required,
566
+ )
567
+ )
568
+ trace = CatalogQueryTrace(
569
+ catalog_manifest_sha256=observed_manifest_sha256,
570
+ backend_coordinate=index.backend.descriptor.coordinate,
571
+ question_digest=recommendation.question_digest,
572
+ requirement_digest=recommendation.requirement_digest,
573
+ candidates=tuple(candidates),
574
+ admitted=recommendation.admitted,
575
+ escalated=recommendation.escalated,
576
+ refused=recommendation.refused,
577
+ )
578
+ return CatalogQuerySnapshot(recommendation, trace, index, manifest_bytes, vector_members)
579
+
580
+
581
+ def query_catalog_with_trace(
582
+ *,
583
+ directory: Path,
584
+ expected_catalog_manifest_sha256: str,
585
+ question: str,
586
+ requirement: CatalogRequirement,
587
+ limit: int = 5,
588
+ ) -> tuple[SourceRecommendation, CatalogQueryTrace]:
589
+ """Verify a sealed catalog, answer from it, and return a source-bound trace."""
590
+
591
+ snapshot = query_catalog_snapshot(
592
+ directory=directory,
593
+ expected_catalog_manifest_sha256=expected_catalog_manifest_sha256,
594
+ question=question,
595
+ requirement=requirement,
596
+ limit=limit,
597
+ )
598
+ return snapshot.recommendation, snapshot.trace
599
+
600
+
601
+ def query_retrieval_catalog_with_trace(
602
+ *,
603
+ directory: Path,
604
+ expected_retrieval_manifest_sha256: str,
605
+ neural_backend: EmbeddingBackend | None,
606
+ backend: str,
607
+ allow_lexical_fallback: bool,
608
+ question: str,
609
+ requirement: CatalogRequirement,
610
+ limit: int = 5,
611
+ ) -> tuple[SourceRecommendation, CatalogQueryTraceV2]:
612
+ """Query one verified v2 catalog and bind its exact score-free public evidence."""
613
+
614
+ loaded = load_retrieval_catalog(
615
+ directory,
616
+ expected_manifest_sha256=expected_retrieval_manifest_sha256,
617
+ neural_backend=neural_backend,
618
+ )
619
+ entry_ids, representation, backend_coordinate, fallback_reason = _rank_retrieval(
620
+ loaded,
621
+ question,
622
+ backend=backend,
623
+ allow_lexical_fallback=allow_lexical_fallback,
624
+ limit=limit,
625
+ )
626
+ recommendation = _recommend_loaded(
627
+ loaded,
628
+ entry_ids,
629
+ question=question,
630
+ requirement=requirement,
631
+ )
632
+ selected_manifest = (
633
+ loaded.manifest.neural if representation == "neural" else loaded.manifest.lexical
634
+ )
635
+ if selected_manifest is None: # pragma: no cover - guarded by the retrieval result contract
636
+ raise SourceContractError(
637
+ "RETRIEVAL_BACKEND", "trace.representation", "selected representation is absent"
638
+ )
639
+ entries_by_coordinate = {entry.coordinate: entry for entry in loaded.lexical.entries}
640
+ trace = CatalogQueryTraceV2(
641
+ retrieval_manifest_sha256=expected_retrieval_manifest_sha256,
642
+ representation=representation,
643
+ representation_artifact_sha256=selected_manifest.artifact_sha256,
644
+ backend_coordinate=backend_coordinate,
645
+ fallback_reason=fallback_reason,
646
+ requested_backend=backend,
647
+ lexical_fallback_allowed=allow_lexical_fallback,
648
+ question_digest=recommendation.question_digest,
649
+ requirement_digest=recommendation.requirement_digest,
650
+ candidates=_trace_candidates(recommendation, entries_by_coordinate),
651
+ admitted=recommendation.admitted,
652
+ escalated=recommendation.escalated,
653
+ refused=recommendation.refused,
654
+ )
655
+ return recommendation, trace
656
+
657
+
658
+ def _recommend_loaded(
659
+ loaded: LoadedRetrievalCatalog,
660
+ entry_ids: tuple[str, ...],
661
+ *,
662
+ question: str,
663
+ requirement: CatalogRequirement,
664
+ ) -> SourceRecommendation:
665
+ by_id = {entry.entry_id: entry for entry in loaded.lexical.entries}
666
+ entries = tuple(by_id[entry_id] for entry_id in entry_ids)
667
+ verdicts = gate_entries(entries, requirement=requirement)
668
+ sorted_into = {
669
+ field: tuple(item.entry_coordinate for item in verdicts if item.disposition == disposition)
670
+ for field, disposition in RECOMMENDATION_BUCKETS
671
+ }
672
+ return SourceRecommendation(
673
+ question_digest=canonical_sha256({"question": question}),
674
+ requirement_digest=requirement.digest,
675
+ candidates=verdicts,
676
+ **sorted_into,
677
+ )
678
+
679
+
680
+ def _trace_candidates(
681
+ recommendation: SourceRecommendation,
682
+ entries_by_coordinate: dict[str, Any],
683
+ ) -> tuple[TracedCandidate, ...]:
684
+ candidates: list[TracedCandidate] = []
685
+ for rank, verdict in enumerate(recommendation.candidates, start=1):
686
+ entry = entries_by_coordinate[verdict.entry_coordinate]
687
+ candidates.append(
688
+ TracedCandidate(
689
+ rank=rank,
690
+ entry_coordinate=entry.coordinate,
691
+ entry_digest=entry.digest,
692
+ facts_digest=entry.facts_digest,
693
+ disposition=verdict.disposition,
694
+ publisher=entry.publisher,
695
+ title=entry.title,
696
+ data_formats=entry.data_formats,
697
+ update_frequency_seconds=entry.observation.update_frequency_seconds,
698
+ access_kind=entry.access_kind,
699
+ authentication_required=entry.authentication_required,
700
+ )
701
+ )
702
+ return tuple(candidates)
703
+
704
+
705
+ # ----------------------------------------------------------------------------------------------
706
+ # The packed generation's query evidence
707
+ # ----------------------------------------------------------------------------------------------
708
+
709
+
710
+ def _packed_disposition(decision: CatalogEntryV2Decision) -> str:
711
+ """Project the v2 fact authority's verdict onto the three governed answer dispositions.
712
+
713
+ ``evaluate_catalog_entry_v2`` is the authority; nothing here re-adjudicates rights. Unclear
714
+ rights are ``flagged`` there and become ``human_escalation_required`` here, because a human
715
+ decides them; everything else follows the recommendation the fact authority already reached.
716
+ """
717
+
718
+ if decision.rights_disposition == "flagged":
719
+ return "human_escalation_required"
720
+ return "admitted" if decision.recommendation == "use_source" else "refused"
721
+
722
+
723
+ @dataclass(frozen=True)
724
+ class PackedTracedCandidate:
725
+ """One ordered identity with the fact disposition it earned. No ranking number, no key."""
726
+
727
+ rank: int
728
+ entry_coordinate: str
729
+ entry_digest: str
730
+ semantic_facts_digest: str
731
+ disposition: str
732
+ recommendation: str
733
+ filters_match: bool
734
+
735
+ def to_dict(self) -> dict[str, Any]:
736
+ return {
737
+ "rank": self.rank,
738
+ "entry_coordinate": self.entry_coordinate,
739
+ "entry_digest": self.entry_digest,
740
+ "semantic_facts_digest": self.semantic_facts_digest,
741
+ "disposition": self.disposition,
742
+ "recommendation": self.recommendation,
743
+ "filters_match": self.filters_match,
744
+ }
745
+
746
+
747
+ @dataclass(frozen=True)
748
+ class CatalogPackedQueryTrace:
749
+ """Canonical evidence for one bounded search of one authenticated packed generation.
750
+
751
+ It binds what was searched (the head and the receipt coordinate), what was read (the range
752
+ manifests and every accelerator and pack the search opened), how it was searched (the backend
753
+ coordinate and the four layers), how much it was allowed to cost and how much it did cost, and
754
+ what the facts decided about each ordered identity.
755
+
756
+ It carries no score, no provider record key and no filesystem path -- an answer's evidence is
757
+ about identities and facts, and a similarity number in a trace is a similarity number a later
758
+ reader will eventually treat as a decision.
759
+ """
760
+
761
+ head_sha256: str
762
+ receipt_coordinate_sha256: str
763
+ backend_coordinate: str
764
+ layers: tuple[str, ...]
765
+ range_manifest_sha256s: tuple[str, ...]
766
+ vector_member_sha256s: tuple[str, ...]
767
+ posting_member_sha256s: tuple[str, ...]
768
+ bound_member_sha256s: tuple[str, ...]
769
+ work_limits: CatalogSearchWorkLimits
770
+ work_actuals: PackedSearchWork
771
+ status: str
772
+ incomplete_code: str | None
773
+ question_digest: str
774
+ requirement_digest: str
775
+ candidates: tuple[PackedTracedCandidate, ...]
776
+ admitted: tuple[str, ...]
777
+ escalated: tuple[str, ...]
778
+ refused: tuple[str, ...]
779
+ schema_version: str = CATALOG_PACKED_QUERY_TRACE_VERSION
780
+
781
+ def __post_init__(self) -> None:
782
+ if self.schema_version != CATALOG_PACKED_QUERY_TRACE_VERSION:
783
+ raise SourceContractError(
784
+ "VERSION", "trace.schema_version", "unsupported packed trace version"
785
+ )
786
+ for name in (
787
+ "head_sha256",
788
+ "receipt_coordinate_sha256",
789
+ "question_digest",
790
+ "requirement_digest",
791
+ ):
792
+ value = getattr(self, name)
793
+ if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
794
+ raise SourceContractError("SHA256", f"trace.{name}", "must be a lowercase SHA-256")
795
+ if not isinstance(self.backend_coordinate, str) or "@" not in self.backend_coordinate:
796
+ raise SourceContractError(
797
+ "CATALOG_BACKEND",
798
+ "trace.backend_coordinate",
799
+ "must name the exact query backend version",
800
+ )
801
+ if self.layers != EMBEDDING_LAYERS:
802
+ raise SourceContractError(
803
+ "CATALOG_TRACE_LAYERS",
804
+ "trace.layers",
805
+ f"a packed answer scores exactly the four layers {list(EMBEDDING_LAYERS)}",
806
+ )
807
+ for name in (
808
+ "range_manifest_sha256s",
809
+ "vector_member_sha256s",
810
+ "posting_member_sha256s",
811
+ "bound_member_sha256s",
812
+ ):
813
+ values = getattr(self, name)
814
+ if not isinstance(values, tuple) or any(
815
+ not isinstance(value, str) or _DIGEST.fullmatch(value) is None for value in values
816
+ ):
817
+ raise SourceContractError(
818
+ "CATALOG_TRACE_MEMBERS",
819
+ f"trace.{name}",
820
+ "must be an immutable tuple of lowercase SHA-256 digests",
821
+ )
822
+ if not isinstance(self.work_limits, CatalogSearchWorkLimits) or not isinstance(
823
+ self.work_actuals, PackedSearchWork
824
+ ):
825
+ raise SourceContractError(
826
+ "CATALOG_TRACE_WORK",
827
+ "trace.work_limits",
828
+ "a packed answer states the closed work contract and the work it actually did",
829
+ )
830
+ if self.status not in {"complete", "incomplete"}:
831
+ raise SourceContractError(
832
+ "CATALOG_TRACE_STATUS", "trace.status", "a search is complete or incomplete"
833
+ )
834
+ if self.status == "complete":
835
+ if self.incomplete_code is not None:
836
+ raise SourceContractError(
837
+ "CATALOG_TRACE_STATUS",
838
+ "trace.incomplete_code",
839
+ "a complete search names no exhausted budget",
840
+ )
841
+ elif (
842
+ self.incomplete_code not in PACKED_INCOMPLETE_CODES
843
+ or self.candidates
844
+ or self.admitted
845
+ or self.escalated
846
+ or self.refused
847
+ ):
848
+ raise SourceContractError(
849
+ "CATALOG_TRACE_STATUS",
850
+ "trace.candidates",
851
+ "an incomplete search names the budget that closed and offers no ranked identity",
852
+ )
853
+ if not isinstance(self.candidates, tuple) or any(
854
+ type(item) is not PackedTracedCandidate for item in self.candidates
855
+ ):
856
+ raise SourceContractError(
857
+ "CATALOG_TRACE_CANDIDATES",
858
+ "trace.candidates",
859
+ "must be an immutable tuple of strict packed candidates",
860
+ )
861
+ if tuple(item.rank for item in self.candidates) != tuple(
862
+ range(1, len(self.candidates) + 1)
863
+ ):
864
+ raise SourceContractError(
865
+ "CATALOG_TRACE_RANK",
866
+ "trace.candidates",
867
+ "ranks must be contiguous and ordered from one",
868
+ )
869
+ coordinates: list[str] = []
870
+ dispositions = {disposition for _field, disposition in RECOMMENDATION_BUCKETS}
871
+ for position, item in enumerate(self.candidates):
872
+ if (
873
+ not isinstance(item.entry_coordinate, str)
874
+ or "@" not in item.entry_coordinate
875
+ or item.entry_coordinate in coordinates
876
+ ):
877
+ raise SourceContractError(
878
+ "CATALOG_TRACE_CANDIDATES",
879
+ f"trace.candidates[{position}].entry_coordinate",
880
+ "must be a unique versioned identity",
881
+ )
882
+ coordinates.append(item.entry_coordinate)
883
+ for name in ("entry_digest", "semantic_facts_digest"):
884
+ value = getattr(item, name)
885
+ if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
886
+ raise SourceContractError(
887
+ "CATALOG_TRACE_CANDIDATES",
888
+ f"trace.candidates[{position}].{name}",
889
+ "must be a lowercase SHA-256",
890
+ )
891
+ if item.disposition not in dispositions:
892
+ raise SourceContractError(
893
+ "CATALOG_TRACE_CANDIDATES",
894
+ f"trace.candidates[{position}].disposition",
895
+ "must be a governed facts disposition",
896
+ )
897
+ for field, disposition in RECOMMENDATION_BUCKETS:
898
+ observed = getattr(self, field)
899
+ if not isinstance(observed, tuple) or any(type(item) is not str for item in observed):
900
+ raise SourceContractError(
901
+ "CATALOG_TRACE_PARTITION",
902
+ f"trace.{field}",
903
+ "must be an immutable tuple of candidate identities",
904
+ )
905
+ expected = tuple(
906
+ item.entry_coordinate for item in self.candidates if item.disposition == disposition
907
+ )
908
+ if observed != expected:
909
+ raise SourceContractError(
910
+ "CATALOG_TRACE_PARTITION",
911
+ f"trace.{field}",
912
+ "must exactly preserve the ordered candidates with this disposition",
913
+ )
914
+
915
+ @property
916
+ def canonical_bytes(self) -> bytes:
917
+ return canonical_json_bytes(self.to_dict())
918
+
919
+ @property
920
+ def digest(self) -> str:
921
+ return sha256_bytes(self.canonical_bytes)
922
+
923
+ def to_dict(self) -> dict[str, Any]:
924
+ return {
925
+ "schema_version": self.schema_version,
926
+ "head_sha256": self.head_sha256,
927
+ "receipt_coordinate_sha256": self.receipt_coordinate_sha256,
928
+ "backend_coordinate": self.backend_coordinate,
929
+ "layers": list(self.layers),
930
+ "range_manifest_sha256s": list(self.range_manifest_sha256s),
931
+ "vector_member_sha256s": list(self.vector_member_sha256s),
932
+ "posting_member_sha256s": list(self.posting_member_sha256s),
933
+ "bound_member_sha256s": list(self.bound_member_sha256s),
934
+ "work_limits": self.work_limits.to_dict(),
935
+ "work_actuals": self.work_actuals.to_dict(),
936
+ "status": self.status,
937
+ "incomplete_code": self.incomplete_code,
938
+ "question_digest": self.question_digest,
939
+ "requirement_digest": self.requirement_digest,
940
+ "candidates": [item.to_dict() for item in self.candidates],
941
+ "admitted": list(self.admitted),
942
+ "escalated": list(self.escalated),
943
+ "refused": list(self.refused),
944
+ }
945
+
946
+
947
+ @dataclass(frozen=True)
948
+ class PackedCatalogAnswer:
949
+ """One packed answer: its evidence, the fact verdicts behind it, and the entries themselves."""
950
+
951
+ status: str
952
+ incomplete_code: str | None
953
+ trace: CatalogPackedQueryTrace
954
+ decisions: tuple[CatalogEntryV2Decision, ...]
955
+ entries: tuple[CatalogEntryV2, ...]
956
+
957
+
958
+ def query_packed_catalog_with_trace(
959
+ *,
960
+ directory: Path,
961
+ expected_head_sha256: str,
962
+ receipt: Any,
963
+ backend: EmbeddingBackend,
964
+ question: str,
965
+ requirement: CatalogRequirement,
966
+ work_limits: CatalogSearchWorkLimits,
967
+ fact_filters: tuple[CatalogFactFilter, ...] = (),
968
+ limit: int = 5,
969
+ ) -> PackedCatalogAnswer:
970
+ """Rank one authenticated packed generation, then decide each ordered identity on facts.
971
+
972
+ The ranking is bounded and exact and knows nothing about facts; the fact authority is
973
+ ``evaluate_catalog_entry_v2`` and knows nothing about the ranking. ``requirement`` is bound into
974
+ the evidence so an answer cites what it was asked for, and the structured fact filters the
975
+ caller supplied are passed to that authority unchanged.
976
+ """
977
+
978
+ if not isinstance(requirement, CatalogRequirement):
979
+ raise SourceContractError("TYPE", "requirement", "must be a CatalogRequirement")
980
+ if not isinstance(fact_filters, tuple):
981
+ raise SourceContractError("TYPE", "fact_filters", "must be an immutable tuple")
982
+ result = rank_packed_entries(
983
+ root=directory,
984
+ expected_head_sha256=expected_head_sha256,
985
+ receipt=receipt,
986
+ question=question,
987
+ backend=backend,
988
+ limit=limit,
989
+ work_limits=work_limits,
990
+ )
991
+ decisions: list[CatalogEntryV2Decision] = []
992
+ candidates: list[PackedTracedCandidate] = []
993
+ for ranked in result.entries:
994
+ decision = evaluate_catalog_entry_v2(ranked.entry, filters=fact_filters)
995
+ decisions.append(decision)
996
+ candidates.append(
997
+ PackedTracedCandidate(
998
+ rank=ranked.rank,
999
+ entry_coordinate=ranked.entry_coordinate,
1000
+ entry_digest=ranked.entry_digest,
1001
+ semantic_facts_digest=ranked.semantic_facts_digest,
1002
+ disposition=_packed_disposition(decision),
1003
+ recommendation=decision.recommendation,
1004
+ filters_match=decision.filters_match,
1005
+ )
1006
+ )
1007
+ sorted_into = {
1008
+ field: tuple(
1009
+ item.entry_coordinate for item in candidates if item.disposition == disposition
1010
+ )
1011
+ for field, disposition in RECOMMENDATION_BUCKETS
1012
+ }
1013
+ trace = CatalogPackedQueryTrace(
1014
+ head_sha256=result.head_sha256,
1015
+ receipt_coordinate_sha256=result.receipt_coordinate_sha256,
1016
+ backend_coordinate=result.backend_coordinate,
1017
+ layers=EMBEDDING_LAYERS,
1018
+ range_manifest_sha256s=result.range_manifest_sha256s,
1019
+ vector_member_sha256s=result.vector_member_sha256s,
1020
+ posting_member_sha256s=result.posting_member_sha256s,
1021
+ bound_member_sha256s=result.bound_member_sha256s,
1022
+ work_limits=result.limits,
1023
+ work_actuals=result.work,
1024
+ status=result.status,
1025
+ incomplete_code=result.incomplete_code,
1026
+ question_digest=result.question_digest,
1027
+ requirement_digest=requirement.digest,
1028
+ candidates=tuple(candidates),
1029
+ **sorted_into,
1030
+ )
1031
+ return PackedCatalogAnswer(
1032
+ status=result.status,
1033
+ incomplete_code=result.incomplete_code,
1034
+ trace=trace,
1035
+ decisions=tuple(decisions),
1036
+ entries=tuple(ranked.entry for ranked in result.entries),
1037
+ )