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.
- mostlyright/data_harness/__init__.py +158 -0
- mostlyright/data_harness/acquisition/__init__.py +55 -0
- mostlyright/data_harness/acquisition/http.py +2773 -0
- mostlyright/data_harness/acquisition/parsing.py +809 -0
- mostlyright/data_harness/acquisition/ranges.py +495 -0
- mostlyright/data_harness/acquisition/result_download.py +360 -0
- mostlyright/data_harness/acquisition/retention_admission.py +248 -0
- mostlyright/data_harness/acquisition/sandbox.py +4888 -0
- mostlyright/data_harness/acquisition/url_policy.py +530 -0
- mostlyright/data_harness/agent_runtime.py +2743 -0
- mostlyright/data_harness/assets/logo-ink.svg +31 -0
- mostlyright/data_harness/backends/__init__.py +28 -0
- mostlyright/data_harness/backends/pandas_backend.py +350 -0
- mostlyright/data_harness/backends/polars_backend.py +366 -0
- mostlyright/data_harness/backends/protocol.py +124 -0
- mostlyright/data_harness/backends/reference.py +83 -0
- mostlyright/data_harness/backends/registry.py +55 -0
- mostlyright/data_harness/backends/restrictions.py +126 -0
- mostlyright/data_harness/canonical.py +333 -0
- mostlyright/data_harness/catalog_job.py +625 -0
- mostlyright/data_harness/cli.py +5398 -0
- mostlyright/data_harness/contracts.py +53 -0
- mostlyright/data_harness/coordinator.py +1307 -0
- mostlyright/data_harness/deploy.py +924 -0
- mostlyright/data_harness/deploy_target.py +312 -0
- mostlyright/data_harness/deployment_evidence.py +1067 -0
- mostlyright/data_harness/event_presentation.py +576 -0
- mostlyright/data_harness/events.py +2152 -0
- mostlyright/data_harness/fast_delimited.py +239 -0
- mostlyright/data_harness/fleet.py +237 -0
- mostlyright/data_harness/formats.py +236 -0
- mostlyright/data_harness/governors.py +1163 -0
- mostlyright/data_harness/hosted_bootstrap.py +972 -0
- mostlyright/data_harness/hosted_crawler.py +1115 -0
- mostlyright/data_harness/hosted_crawler_container_smoke.py +351 -0
- mostlyright/data_harness/hosted_crawler_fetch.py +423 -0
- mostlyright/data_harness/hosted_crawler_job.py +1277 -0
- mostlyright/data_harness/hosted_crawler_protocol.py +676 -0
- mostlyright/data_harness/hosted_dataset.py +1500 -0
- mostlyright/data_harness/hosted_deploy.py +3037 -0
- mostlyright/data_harness/hosted_handoff.py +62 -0
- mostlyright/data_harness/hosted_ingestion_contract.py +504 -0
- mostlyright/data_harness/hosted_ingestion_job.py +356 -0
- mostlyright/data_harness/hosted_ingestion_job_smoke.py +40 -0
- mostlyright/data_harness/hosted_session_container_smoke.py +194 -0
- mostlyright/data_harness/hosted_session_worker.py +3554 -0
- mostlyright/data_harness/hosted_session_worker_job_smoke.py +46 -0
- mostlyright/data_harness/hosted_worker.py +6784 -0
- mostlyright/data_harness/ingestion/__init__.py +56 -0
- mostlyright/data_harness/ingestion/contracts.py +461 -0
- mostlyright/data_harness/ingestion/faults.py +42 -0
- mostlyright/data_harness/ingestion/gcs_store.py +1162 -0
- mostlyright/data_harness/ingestion/spool.py +130 -0
- mostlyright/data_harness/ingestion/store.py +885 -0
- mostlyright/data_harness/key_seam.py +434 -0
- mostlyright/data_harness/linux_process_boundary.py +262 -0
- mostlyright/data_harness/local_contracts.py +2880 -0
- mostlyright/data_harness/local_search/__init__.py +5 -0
- mostlyright/data_harness/local_search/build_index.py +1087 -0
- mostlyright/data_harness/local_search/contracts.py +920 -0
- mostlyright/data_harness/local_search/query_trace.py +266 -0
- mostlyright/data_harness/local_search/retrieval.py +700 -0
- mostlyright/data_harness/local_search/sealed.py +474 -0
- mostlyright/data_harness/local_search/service.py +784 -0
- mostlyright/data_harness/nbrender/CONTRACT.md +212 -0
- mostlyright/data_harness/nbrender/__init__.py +12 -0
- mostlyright/data_harness/nbrender/chrome.py +359 -0
- mostlyright/data_harness/nbrender/code_body.py +266 -0
- mostlyright/data_harness/nbrender/document.py +407 -0
- mostlyright/data_harness/nbrender/frame.py +275 -0
- mostlyright/data_harness/nbrender/interactive.py +337 -0
- mostlyright/data_harness/nbrender/markdown_body.py +477 -0
- mostlyright/data_harness/nbrender/mr_components.py +134 -0
- mostlyright/data_harness/nbrender/outputs_data.py +595 -0
- mostlyright/data_harness/nbrender/outputs_rich.py +906 -0
- mostlyright/data_harness/nbrender/outputs_source.py +260 -0
- mostlyright/data_harness/nbrender/outputs_stage.py +176 -0
- mostlyright/data_harness/nbrender/outputs_text.py +400 -0
- mostlyright/data_harness/nbrender/parse.py +394 -0
- mostlyright/data_harness/nbrender/status.py +40 -0
- mostlyright/data_harness/nbrender/tokens.py +1295 -0
- mostlyright/data_harness/notebook.py +1710 -0
- mostlyright/data_harness/offline.py +2049 -0
- mostlyright/data_harness/operation_registry.py +1007 -0
- mostlyright/data_harness/operator_setup.py +239 -0
- mostlyright/data_harness/pipeline.py +6428 -0
- mostlyright/data_harness/plan_graph.py +2026 -0
- mostlyright/data_harness/preparation/__init__.py +104 -0
- mostlyright/data_harness/preparation/contracts.py +1017 -0
- mostlyright/data_harness/preparation/engine.py +221 -0
- mostlyright/data_harness/preparation/errors.py +14 -0
- mostlyright/data_harness/preparation/gates.py +751 -0
- mostlyright/data_harness/preparation/joins.py +574 -0
- mostlyright/data_harness/preparation/profile.py +384 -0
- mostlyright/data_harness/preparation/table.py +217 -0
- mostlyright/data_harness/preparation/transforms.py +568 -0
- mostlyright/data_harness/progress_events.py +534 -0
- mostlyright/data_harness/readers/__init__.py +46 -0
- mostlyright/data_harness/readers/containers.py +963 -0
- mostlyright/data_harness/readers/contracts.py +542 -0
- mostlyright/data_harness/readers/delimited.py +257 -0
- mostlyright/data_harness/readers/grib2/__init__.py +33 -0
- mostlyright/data_harness/readers/grib2/admission.py +722 -0
- mostlyright/data_harness/readers/grib2/decode.py +1009 -0
- mostlyright/data_harness/readers/grib2/geometry.py +1133 -0
- mostlyright/data_harness/readers/grib2/portable_math.py +501 -0
- mostlyright/data_harness/readers/json_tabular.py +485 -0
- mostlyright/data_harness/readers/registry.py +514 -0
- mostlyright/data_harness/readers/samples/README.md +110 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/cities.csv.gz +0 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.0.0/cities_one_stream/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/cities.csv.gz +0 -0
- mostlyright/data_harness/readers/samples/archive.gzip/1.1.0/cities_one_stream/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/cities.tar +0 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.0.0/cities_beside_a_directory_entry/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/cities.tar +0 -0
- mostlyright/data_harness/readers/samples/archive.tar/1.1.0/cities_beside_a_directory_entry/expected.json +24 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/cities.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.0.0/cities_beside_a_second_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.1.0/dwd_semicolon_station_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/dwd-station.zip +0 -0
- mostlyright/data_harness/readers/samples/archive.zip/1.2.0/dwd_semicolon_station_member/expected.json +25 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/cities.csv +3 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/an_ordinary_comma_separated_table/expected.json +23 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/cities.tsv +5 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.0.0/quoted_fields_holding_the_delimiter/expected.json +25 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/expected.json +30 -0
- mostlyright/data_harness/readers/samples/delimited_text/1.1.0/an_hourly_observation_table_served_as_plain_text/observations.csv +5 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/expected.json +44 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.0.0/nested_hourly_observations/stations.json +1 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/expected.json +48 -0
- mostlyright/data_harness/readers/samples/json.tabular/1.1.0/an_observation_stream_served_as_plain_text/observations.ndjson +4 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/cities.xlsx +0 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/an_ordinary_table_beside_a_second_sheet/expected.json +24 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/expected.json +27 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.0.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/expected.json +27 -0
- mostlyright/data_harness/readers/samples/spreadsheet.xlsx/1.1.0/shares_the_workbook_had_already_computed/shares.xlsx +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/README.md +20 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/expected.json +55 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/gfs_2m_temperature/gfs-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/1.0.0/hrrr_categorical_rain/hrrr-categorical-rain.grib2 +0 -0
- mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/expected.json +54 -0
- mostlyright/data_harness/readers/samples/weather.grib2/2.0.0/hrrr_2m_temperature/hrrr-2m-temperature.grib2 +0 -0
- mostlyright/data_harness/readers/samples.py +582 -0
- mostlyright/data_harness/readers/spreadsheet.py +803 -0
- mostlyright/data_harness/readers/tabular.py +510 -0
- mostlyright/data_harness/recipe.py +5321 -0
- mostlyright/data_harness/repair/__init__.py +78 -0
- mostlyright/data_harness/repair/adapters.py +274 -0
- mostlyright/data_harness/repair/contracts.py +872 -0
- mostlyright/data_harness/repair/coordinator.py +1099 -0
- mostlyright/data_harness/repair/errors.py +16 -0
- mostlyright/data_harness/review.py +2533 -0
- mostlyright/data_harness/rowset.py +283 -0
- mostlyright/data_harness/serving.py +1975 -0
- mostlyright/data_harness/serving_edge.py +590 -0
- mostlyright/data_harness/serving_http.py +1031 -0
- mostlyright/data_harness/session_probes.py +759 -0
- mostlyright/data_harness/signing.py +101 -0
- mostlyright/data_harness/source_discovery.py +898 -0
- mostlyright/data_harness/sources/__init__.py +209 -0
- mostlyright/data_harness/sources/_adapter_steps.py +213 -0
- mostlyright/data_harness/sources/adapters.py +1214 -0
- mostlyright/data_harness/sources/cadence.py +1428 -0
- mostlyright/data_harness/sources/cadence_emission.py +453 -0
- mostlyright/data_harness/sources/cadence_history.py +546 -0
- mostlyright/data_harness/sources/catalog/__init__.py +17 -0
- mostlyright/data_harness/sources/catalog/admission.py +477 -0
- mostlyright/data_harness/sources/catalog/authoring.py +1701 -0
- mostlyright/data_harness/sources/catalog/authoring_policy.py +701 -0
- mostlyright/data_harness/sources/catalog/authoring_shards.py +1217 -0
- mostlyright/data_harness/sources/catalog/bounded_io.py +231 -0
- mostlyright/data_harness/sources/catalog/channel.py +523 -0
- mostlyright/data_harness/sources/catalog/channel_client.py +296 -0
- mostlyright/data_harness/sources/catalog/contracts.py +825 -0
- mostlyright/data_harness/sources/catalog/coverage.py +137 -0
- mostlyright/data_harness/sources/catalog/delta.py +1340 -0
- mostlyright/data_harness/sources/catalog/embedding.py +532 -0
- mostlyright/data_harness/sources/catalog/entry_v2.py +1182 -0
- mostlyright/data_harness/sources/catalog/fill.py +3889 -0
- mostlyright/data_harness/sources/catalog/fill_partitions.py +459 -0
- mostlyright/data_harness/sources/catalog/fill_staging.py +1105 -0
- mostlyright/data_harness/sources/catalog/gating.py +374 -0
- mostlyright/data_harness/sources/catalog/generation_receipt.py +1607 -0
- mostlyright/data_harness/sources/catalog/harvest/__init__.py +7 -0
- mostlyright/data_harness/sources/catalog/harvest/ckan.py +384 -0
- mostlyright/data_harness/sources/catalog/harvest/datagov_v4.py +798 -0
- mostlyright/data_harness/sources/catalog/harvest/protocol.py +964 -0
- mostlyright/data_harness/sources/catalog/harvest/sdmx.py +445 -0
- mostlyright/data_harness/sources/catalog/harvest/stac.py +384 -0
- mostlyright/data_harness/sources/catalog/health.py +447 -0
- mostlyright/data_harness/sources/catalog/hosted_catalog.py +105 -0
- mostlyright/data_harness/sources/catalog/identity_history.py +1549 -0
- mostlyright/data_harness/sources/catalog/neural.py +1618 -0
- mostlyright/data_harness/sources/catalog/packed_catalog.py +2345 -0
- mostlyright/data_harness/sources/catalog/packed_retrieval.py +1517 -0
- mostlyright/data_harness/sources/catalog/packed_writer.py +2802 -0
- mostlyright/data_harness/sources/catalog/query_trace.py +1037 -0
- mostlyright/data_harness/sources/catalog/recommend.py +171 -0
- mostlyright/data_harness/sources/catalog/retrieval.py +230 -0
- mostlyright/data_harness/sources/catalog/retrieval_manifest.py +995 -0
- mostlyright/data_harness/sources/catalog/rights_decisions.py +254 -0
- mostlyright/data_harness/sources/catalog/sealed.py +560 -0
- mostlyright/data_harness/sources/catalog/search.py +230 -0
- mostlyright/data_harness/sources/catalog/streaming_delta.py +1097 -0
- mostlyright/data_harness/sources/catalog/update.py +891 -0
- mostlyright/data_harness/sources/collections.py +815 -0
- mostlyright/data_harness/sources/contracts.py +2223 -0
- mostlyright/data_harness/sources/deletion.py +761 -0
- mostlyright/data_harness/sources/fitness.py +162 -0
- mostlyright/data_harness/sources/governance.py +163 -0
- mostlyright/data_harness/sources/hosted.py +173 -0
- mostlyright/data_harness/sources/integration.py +218 -0
- mostlyright/data_harness/sources/range_reader.py +418 -0
- mostlyright/data_harness/sources/registry.py +514 -0
- mostlyright/data_harness/sources/rights_rule.py +59 -0
- mostlyright/data_harness/sources/source_cadence_vectors.v1.json +1 -0
- mostlyright/data_harness/sources/sports.py +521 -0
- mostlyright/data_harness/sources/stream.py +524 -0
- mostlyright/data_harness/sources/stream_connector.py +418 -0
- mostlyright/data_harness/sources/stream_recorder.py +1404 -0
- mostlyright/data_harness/studio_boundary.py +2019 -0
- mostlyright/data_harness/thin/__init__.py +37 -0
- mostlyright/data_harness/thin/acquire.py +1137 -0
- mostlyright/data_harness/thin/acquire_cancel.py +579 -0
- mostlyright/data_harness/thin/approvals.py +617 -0
- mostlyright/data_harness/thin/commands.py +406 -0
- mostlyright/data_harness/thin/download.py +194 -0
- mostlyright/data_harness/thin/narrative.py +589 -0
- mostlyright/data_harness/thin/parity.py +1070 -0
- mostlyright/data_harness/thin/propose.py +2759 -0
- mostlyright/data_harness/thin/research.py +1663 -0
- mostlyright/data_harness/thin/router.py +924 -0
- mostlyright/data_harness/thin/runs.py +519 -0
- mostlyright/data_harness/thin/session.py +281 -0
- mostlyright/data_harness/thin/stream.py +501 -0
- mostlyright/data_harness/thin/transport.py +187 -0
- mostlyright/data_harness/thin/vocabulary.py +368 -0
- mostlyright/data_harness/thin/workers.py +164 -0
- mostlyright/data_harness/ucum/TABLE-PIN.json +40 -0
- mostlyright/data_harness/ucum/ucum-subset.v1.json +632 -0
- mostlyright/data_harness/unit_flow.py +927 -0
- mostlyright/data_harness/units.py +572 -0
- mostlyright/data_harness/ux/__init__.py +9 -0
- mostlyright/data_harness/ux/approve.py +485 -0
- mostlyright/data_harness/ux/author_yaml.py +597 -0
- mostlyright/data_harness/ux/cloud_auth.py +447 -0
- mostlyright/data_harness/ux/commands/__init__.py +260 -0
- mostlyright/data_harness/ux/commands/approve.py +136 -0
- mostlyright/data_harness/ux/commands/auth.py +744 -0
- mostlyright/data_harness/ux/commands/author.py +79 -0
- mostlyright/data_harness/ux/commands/catalog_author.py +403 -0
- mostlyright/data_harness/ux/commands/catalog_fill.py +523 -0
- mostlyright/data_harness/ux/commands/catalog_harvest.py +545 -0
- mostlyright/data_harness/ux/commands/catalog_publish.py +1838 -0
- mostlyright/data_harness/ux/commands/catalog_search.py +71 -0
- mostlyright/data_harness/ux/commands/catalog_update.py +437 -0
- mostlyright/data_harness/ux/commands/deploy.py +134 -0
- mostlyright/data_harness/ux/commands/deploy_dataset.py +98 -0
- mostlyright/data_harness/ux/commands/deploy_plan.py +105 -0
- mostlyright/data_harness/ux/commands/deploy_status.py +104 -0
- mostlyright/data_harness/ux/commands/diff.py +74 -0
- mostlyright/data_harness/ux/commands/index.py +84 -0
- mostlyright/data_harness/ux/commands/inventory.py +47 -0
- mostlyright/data_harness/ux/commands/list_builds.py +143 -0
- mostlyright/data_harness/ux/commands/login.py +63 -0
- mostlyright/data_harness/ux/commands/peek.py +236 -0
- mostlyright/data_harness/ux/commands/plan_check.py +90 -0
- mostlyright/data_harness/ux/commands/preflight.py +97 -0
- mostlyright/data_harness/ux/commands/record.py +107 -0
- mostlyright/data_harness/ux/commands/review_setup.py +47 -0
- mostlyright/data_harness/ux/commands/search.py +440 -0
- mostlyright/data_harness/ux/commands/show.py +61 -0
- mostlyright/data_harness/ux/commands/whoami.py +37 -0
- mostlyright/data_harness/ux/credential_native.py +551 -0
- mostlyright/data_harness/ux/credential_store.py +1055 -0
- mostlyright/data_harness/ux/credentials.py +631 -0
- mostlyright/data_harness/ux/diffing.py +444 -0
- mostlyright/data_harness/ux/headline.py +671 -0
- mostlyright/data_harness/ux/hosted_acquisition.py +974 -0
- mostlyright/data_harness/ux/hosted_run_status.py +619 -0
- mostlyright/data_harness/ux/inventory.py +427 -0
- mostlyright/data_harness/ux/local_review.py +375 -0
- mostlyright/data_harness/ux/login.py +691 -0
- mostlyright/data_harness/ux/path_kind.py +147 -0
- mostlyright/data_harness/ux/peek.py +1000 -0
- mostlyright/data_harness/ux/plain_file.py +178 -0
- mostlyright/data_harness/ux/plan_check.py +311 -0
- mostlyright/data_harness/ux/preflight.py +918 -0
- mostlyright/data_harness/ux/readers.py +1124 -0
- mostlyright/data_harness/ux/remediation.py +2195 -0
- mostlyright/data_harness/ux/render.py +657 -0
- mostlyright/data_harness/ux/workload.py +1077 -0
- mostlyright/data_harness/viewer.py +3713 -0
- mostlyright/data_harness/visual_run/__init__.py +83 -0
- mostlyright/data_harness/visual_run/authoring.py +235 -0
- mostlyright/data_harness/visual_run/contracts.py +673 -0
- mostlyright/data_harness/visual_run/materialize.py +486 -0
- mostlyright/data_harness/visual_run/observations.py +874 -0
- mostlyright/data_harness/visual_run/query.py +259 -0
- mostlyright/data_harness/visual_run/reducer.py +280 -0
- mostlyright/data_harness/visual_run/sdk.py +892 -0
- mostlyright/data_harness/visual_run/store.py +584 -0
- mostlyright/data_harness/visual_run/transport.py +239 -0
- mostlyright/data_harness/watch.py +2999 -0
- mostlyright_data-0.9.0.dist-info/METADATA +607 -0
- mostlyright_data-0.9.0.dist-info/RECORD +314 -0
- mostlyright_data-0.9.0.dist-info/WHEEL +4 -0
- 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
|
+
)
|