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,171 @@
|
|
|
1
|
+
"""The agent-facing call: a question in, ranked and gated source recommendations out.
|
|
2
|
+
|
|
3
|
+
This is the composition root, and the only module in the package that names both retrieval and
|
|
4
|
+
gating. What crosses the boundary is ``tuple[str, ...]``. No number reaches this module from
|
|
5
|
+
``rank_entry_ids``, and none
|
|
6
|
+
could be handed onward to ``gate_entries`` even if one did, because neither ``CatalogEntry`` nor
|
|
7
|
+
``CatalogRequirement`` has a field able to hold one. A composition root reaching both sides is
|
|
8
|
+
what a composition root is for; ``gating`` cannot reach ``retrieval``.
|
|
9
|
+
|
|
10
|
+
The order is a trust boundary and must not be rearranged: retrieval narrows the field, the facts
|
|
11
|
+
gate decides. The candidate list is never filtered by anything derived from retrieval except
|
|
12
|
+
``limit``, which truncates.
|
|
13
|
+
|
|
14
|
+
``to_dict`` is canonical-JSON-safe on purpose. One set of facts should be able to drive both a
|
|
15
|
+
sentence a person reads and a machine-readable payload, without a second code path inventing a
|
|
16
|
+
slightly different answer.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from mostlyright.data_harness.canonical import canonical_sha256
|
|
26
|
+
from mostlyright.data_harness.sources.catalog.gating import (
|
|
27
|
+
CATALOG_DISPOSITIONS,
|
|
28
|
+
CatalogGateVerdict,
|
|
29
|
+
CatalogRequirement,
|
|
30
|
+
gate_entries,
|
|
31
|
+
)
|
|
32
|
+
from mostlyright.data_harness.sources.catalog.retrieval import rank_entry_ids
|
|
33
|
+
from mostlyright.data_harness.sources.catalog.sealed import SealedEmbeddingIndex
|
|
34
|
+
from mostlyright.data_harness.sources.contracts import SourceContractError, _CanonicalContract
|
|
35
|
+
|
|
36
|
+
RECOMMENDATION_CONTRACT_VERSION = "harness-source-recommendation.v1"
|
|
37
|
+
|
|
38
|
+
# The shape the refusal below names. Checking the length alone made the message a description of
|
|
39
|
+
# a check that was not being run, and every other digest field in this package is checked here.
|
|
40
|
+
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
41
|
+
|
|
42
|
+
# Which field on this contract answers for which verdict disposition. Written once and read by
|
|
43
|
+
# both the check below and `recommend_sources`, so the two can never sort into different buckets.
|
|
44
|
+
# `::test_every_gate_disposition_has_a_field_that_answers_for_it` fails if `gating` grows a fourth
|
|
45
|
+
# disposition this table does not name.
|
|
46
|
+
RECOMMENDATION_BUCKETS = (
|
|
47
|
+
("admitted", "admitted"),
|
|
48
|
+
("escalated", "human_escalation_required"),
|
|
49
|
+
("refused", "refused"),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(frozen=True)
|
|
54
|
+
class SourceRecommendation(_CanonicalContract):
|
|
55
|
+
"""What the catalog can say about one question, with every decision cited.
|
|
56
|
+
|
|
57
|
+
``question_digest`` binds the answer to the exact question without putting a stranger's free
|
|
58
|
+
text into a digest chain. ``requirement_digest`` binds it to what was asked *for*, which is the
|
|
59
|
+
other half of the input and the only thing that distinguishes two answers about the same
|
|
60
|
+
question. It is carried here as well as on each verdict because a question that retrieves
|
|
61
|
+
nothing is a legitimate answer with no verdicts in it, and that answer would otherwise cite no
|
|
62
|
+
requirement at all.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
question_digest: str
|
|
66
|
+
requirement_digest: str
|
|
67
|
+
candidates: tuple[CatalogGateVerdict, ...]
|
|
68
|
+
admitted: tuple[str, ...]
|
|
69
|
+
escalated: tuple[str, ...]
|
|
70
|
+
refused: tuple[str, ...]
|
|
71
|
+
schema_version: str = RECOMMENDATION_CONTRACT_VERSION
|
|
72
|
+
|
|
73
|
+
def __post_init__(self) -> None:
|
|
74
|
+
if self.schema_version != RECOMMENDATION_CONTRACT_VERSION:
|
|
75
|
+
raise SourceContractError(
|
|
76
|
+
"VERSION",
|
|
77
|
+
"recommendation.schema_version",
|
|
78
|
+
f"must equal {RECOMMENDATION_CONTRACT_VERSION!r}",
|
|
79
|
+
)
|
|
80
|
+
for name in ("question_digest", "requirement_digest"):
|
|
81
|
+
value = getattr(self, name)
|
|
82
|
+
if not isinstance(value, str) or not _SHA256.fullmatch(value):
|
|
83
|
+
raise SourceContractError(
|
|
84
|
+
"SHA256",
|
|
85
|
+
f"recommendation.{name}",
|
|
86
|
+
"must be a lowercase 64-character SHA-256",
|
|
87
|
+
)
|
|
88
|
+
for name in ("candidates", "admitted", "escalated", "refused"):
|
|
89
|
+
if not isinstance(getattr(self, name), tuple):
|
|
90
|
+
raise SourceContractError(
|
|
91
|
+
"TYPE",
|
|
92
|
+
f"recommendation.{name}",
|
|
93
|
+
"must be an immutable tuple",
|
|
94
|
+
)
|
|
95
|
+
for position, verdict in enumerate(self.candidates):
|
|
96
|
+
if not isinstance(verdict, CatalogGateVerdict):
|
|
97
|
+
raise SourceContractError(
|
|
98
|
+
"TYPE",
|
|
99
|
+
f"recommendation.candidates[{position}]",
|
|
100
|
+
"must be a CatalogGateVerdict",
|
|
101
|
+
)
|
|
102
|
+
# One answer, one requirement. A candidate decided against a different requirement
|
|
103
|
+
# from the one this answer cites would make the citation a decoration.
|
|
104
|
+
if verdict.requirement_digest != self.requirement_digest:
|
|
105
|
+
raise SourceContractError(
|
|
106
|
+
"REQUIREMENT_BINDING",
|
|
107
|
+
f"recommendation.candidates[{position}].requirement_digest",
|
|
108
|
+
"a candidate was decided against a different requirement from the one this "
|
|
109
|
+
"recommendation cites",
|
|
110
|
+
)
|
|
111
|
+
# The three lists must be the verdicts *sorted into their own dispositions*, not merely a
|
|
112
|
+
# partition of the same coordinates. Checking only that the union matches let a
|
|
113
|
+
# recommendation name a source under `refused` whose verdict said `admitted` -- two
|
|
114
|
+
# conflicting answers about one source in the same payload, with nothing raised.
|
|
115
|
+
expected: dict[str, list[str]] = {name: [] for name in CATALOG_DISPOSITIONS}
|
|
116
|
+
for verdict in self.candidates:
|
|
117
|
+
expected[verdict.disposition].append(verdict.entry_coordinate)
|
|
118
|
+
for name, disposition in RECOMMENDATION_BUCKETS:
|
|
119
|
+
if sorted(getattr(self, name)) != sorted(expected[disposition]):
|
|
120
|
+
raise SourceContractError(
|
|
121
|
+
"RECOMMENDATION_PARTITION",
|
|
122
|
+
f"recommendation.{name}",
|
|
123
|
+
f"must name exactly the candidates whose verdict is {disposition!r}, each "
|
|
124
|
+
"once; the three lists are the candidates sorted by their own disposition, "
|
|
125
|
+
"not an independently supplied partition of them",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def to_dict(self) -> dict[str, Any]:
|
|
129
|
+
return {
|
|
130
|
+
"schema_version": self.schema_version,
|
|
131
|
+
"question_digest": self.question_digest,
|
|
132
|
+
"requirement_digest": self.requirement_digest,
|
|
133
|
+
"candidates": [item.to_dict() for item in self.candidates],
|
|
134
|
+
"admitted": list(self.admitted),
|
|
135
|
+
"escalated": list(self.escalated),
|
|
136
|
+
"refused": list(self.refused),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def recommend_sources(
|
|
141
|
+
*,
|
|
142
|
+
index: SealedEmbeddingIndex,
|
|
143
|
+
question: str,
|
|
144
|
+
requirement: CatalogRequirement,
|
|
145
|
+
limit: int,
|
|
146
|
+
) -> SourceRecommendation:
|
|
147
|
+
"""Retrieve candidates for this question, then decide each one on facts.
|
|
148
|
+
|
|
149
|
+
A question that retrieves nothing yields an empty recommendation rather than an error: "the
|
|
150
|
+
catalog holds nothing like this" is an answer, and an exception would make a caller unable to
|
|
151
|
+
tell it from a failure.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
if not isinstance(index, SealedEmbeddingIndex):
|
|
155
|
+
raise SourceContractError("TYPE", "index", "must be a SealedEmbeddingIndex")
|
|
156
|
+
entry_ids = rank_entry_ids(index, question, limit=limit)
|
|
157
|
+
by_id = {entry.entry_id: entry for entry in index.entries}
|
|
158
|
+
entries = tuple(by_id[entry_id] for entry_id in entry_ids if entry_id in by_id)
|
|
159
|
+
verdicts = gate_entries(entries, requirement=requirement)
|
|
160
|
+
# Sorted through the same table the contract checks against, so the builder and the check
|
|
161
|
+
# cannot drift into disagreeing about which field answers for which disposition.
|
|
162
|
+
sorted_into = {
|
|
163
|
+
field: tuple(item.entry_coordinate for item in verdicts if item.disposition == disposition)
|
|
164
|
+
for field, disposition in RECOMMENDATION_BUCKETS
|
|
165
|
+
}
|
|
166
|
+
return SourceRecommendation(
|
|
167
|
+
question_digest=canonical_sha256({"question": question}),
|
|
168
|
+
requirement_digest=requirement.digest,
|
|
169
|
+
candidates=verdicts,
|
|
170
|
+
**sorted_into,
|
|
171
|
+
)
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""Retrieval: find candidate sources. Decide nothing.
|
|
2
|
+
|
|
3
|
+
This module ranks catalog entries against a question and returns their identifiers. It returns a
|
|
4
|
+
``tuple[str, ...]`` and nothing else, ever. The numbers it computes to produce that order are
|
|
5
|
+
module-private and die at the return statement, so no caller can consult one and no future edit
|
|
6
|
+
can accidentally route one into a decision.
|
|
7
|
+
|
|
8
|
+
That is a deliberate design choice over the obvious alternative. A ``RankedCandidate`` dataclass
|
|
9
|
+
carrying an unread score field would behave identically today and would quietly break the trust
|
|
10
|
+
boundary: the score would be *available* to the gate, and the separation would rest on
|
|
11
|
+
nobody ever reading it. Making the number impossible to pass is cheaper than policing who reads it.
|
|
12
|
+
|
|
13
|
+
There is no threshold parameter here, and there must never be one. A threshold that admits or
|
|
14
|
+
rejects is an admission decision made by similarity, which is exactly what the facts gate exists
|
|
15
|
+
to prevent. ``limit`` truncates a list; it cannot admit anything.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from mostlyright.data_harness.sources.catalog.contracts import EMBEDDING_LAYERS
|
|
21
|
+
from mostlyright.data_harness.sources.catalog.retrieval_manifest import (
|
|
22
|
+
LoadedRetrievalCatalog,
|
|
23
|
+
LoadedRetrievalRepresentation,
|
|
24
|
+
)
|
|
25
|
+
from mostlyright.data_harness.sources.catalog.sealed import SealedEmbeddingIndex
|
|
26
|
+
from mostlyright.data_harness.sources.contracts import SourceContractError
|
|
27
|
+
|
|
28
|
+
MAX_QUERY_CHARS = 4_096
|
|
29
|
+
MAX_RETRIEVAL_LIMIT = 100
|
|
30
|
+
RETRIEVAL_BACKENDS = frozenset({"lexical", "neural"})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def rank_entry_ids(index: SealedEmbeddingIndex, question: str, *, limit: int) -> tuple[str, ...]:
|
|
34
|
+
"""Return at most ``limit`` catalog entry ids, best match first.
|
|
35
|
+
|
|
36
|
+
Ties break on ``entry_id`` ascending, so the order is total and reproducible -- an eval that
|
|
37
|
+
depends on a coin flip is an eval that flakes. A question with no lexical signal returns an
|
|
38
|
+
empty tuple rather than an arbitrary alphabetical list, so "no signal" stays visibly different
|
|
39
|
+
from "weak signal".
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
if not isinstance(index, SealedEmbeddingIndex):
|
|
43
|
+
raise SourceContractError("TYPE", "index", "must be a SealedEmbeddingIndex")
|
|
44
|
+
if type(limit) is not int or not 1 <= limit <= MAX_RETRIEVAL_LIMIT:
|
|
45
|
+
raise SourceContractError(
|
|
46
|
+
"RETRIEVAL_LIMIT",
|
|
47
|
+
"limit",
|
|
48
|
+
f"must be an integer in [1, {MAX_RETRIEVAL_LIMIT}]",
|
|
49
|
+
)
|
|
50
|
+
if not isinstance(question, str):
|
|
51
|
+
raise SourceContractError("TYPE", "question", "must be a string")
|
|
52
|
+
if len(question) > MAX_QUERY_CHARS:
|
|
53
|
+
raise SourceContractError(
|
|
54
|
+
"RETRIEVAL_QUERY_LIMIT",
|
|
55
|
+
"question",
|
|
56
|
+
f"a question may not exceed {MAX_QUERY_CHARS} characters",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# `SealedEmbeddingIndex` cannot hold a backend that is unsafe to run in-process: it refuses one
|
|
60
|
+
# at construction, not only on the `load` path. That is why the isinstance check above is
|
|
61
|
+
# sufficient here and this module needs no policy of its own -- the precondition is a property
|
|
62
|
+
# of the argument's type rather than a claim about where the caller got it from.
|
|
63
|
+
asked = index.backend.encode(question)
|
|
64
|
+
if not any(asked):
|
|
65
|
+
return ()
|
|
66
|
+
|
|
67
|
+
ordered: list[tuple[int, str]] = []
|
|
68
|
+
for entry in index.entries:
|
|
69
|
+
best = _best_layer_score(index, entry.entry_id, asked)
|
|
70
|
+
if best <= 0:
|
|
71
|
+
continue
|
|
72
|
+
ordered.append((-best, entry.entry_id))
|
|
73
|
+
ordered.sort()
|
|
74
|
+
return tuple(entry_id for _negated, entry_id in ordered[:limit])
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def rank_retrieval_entry_ids(
|
|
78
|
+
catalog: LoadedRetrievalCatalog,
|
|
79
|
+
question: str,
|
|
80
|
+
*,
|
|
81
|
+
backend: str,
|
|
82
|
+
allow_lexical_fallback: bool,
|
|
83
|
+
limit: int,
|
|
84
|
+
) -> tuple[str, ...]:
|
|
85
|
+
"""Order verified v2 entry identities with one explicit representation choice.
|
|
86
|
+
|
|
87
|
+
Selecting ``neural`` never discovers a model. The caller must already have supplied an exact
|
|
88
|
+
backend to ``load_retrieval_catalog``. When it did not, fallback occurs only under the explicit
|
|
89
|
+
boolean; the v2 trace carries the loader's typed reason. Numerical ranking data stays here.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
return _rank_retrieval(
|
|
93
|
+
catalog,
|
|
94
|
+
question,
|
|
95
|
+
backend=backend,
|
|
96
|
+
allow_lexical_fallback=allow_lexical_fallback,
|
|
97
|
+
limit=limit,
|
|
98
|
+
)[0]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _rank_retrieval(
|
|
102
|
+
catalog: LoadedRetrievalCatalog,
|
|
103
|
+
question: str,
|
|
104
|
+
*,
|
|
105
|
+
backend: str,
|
|
106
|
+
allow_lexical_fallback: bool,
|
|
107
|
+
limit: int,
|
|
108
|
+
) -> tuple[tuple[str, ...], str, str, str | None]:
|
|
109
|
+
if not isinstance(catalog, LoadedRetrievalCatalog):
|
|
110
|
+
raise SourceContractError(
|
|
111
|
+
"TYPE", "catalog", "must be a fully verified LoadedRetrievalCatalog"
|
|
112
|
+
)
|
|
113
|
+
if not isinstance(backend, str) or backend not in RETRIEVAL_BACKENDS:
|
|
114
|
+
raise SourceContractError(
|
|
115
|
+
"RETRIEVAL_BACKEND", "backend", "must be either lexical or neural"
|
|
116
|
+
)
|
|
117
|
+
if type(allow_lexical_fallback) is not bool:
|
|
118
|
+
raise SourceContractError("TYPE", "allow_lexical_fallback", "must be a boolean")
|
|
119
|
+
_validate_request(question, limit)
|
|
120
|
+
|
|
121
|
+
selected = catalog.lexical
|
|
122
|
+
representation = "lexical"
|
|
123
|
+
fallback_reason: str | None = None
|
|
124
|
+
if backend == "neural":
|
|
125
|
+
if catalog.neural is not None:
|
|
126
|
+
selected = catalog.neural
|
|
127
|
+
representation = "neural"
|
|
128
|
+
elif not allow_lexical_fallback:
|
|
129
|
+
raise SourceContractError(
|
|
130
|
+
"NEURAL_RETRIEVAL_UNAVAILABLE",
|
|
131
|
+
"backend",
|
|
132
|
+
catalog.fallback_reason or "the verified catalog has no neural representation",
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
fallback_reason = catalog.fallback_reason or "NEURAL_RUNTIME_UNAVAILABLE"
|
|
136
|
+
|
|
137
|
+
entry_ids = _rank_loaded_entry_ids(selected, question, limit=limit)
|
|
138
|
+
return (
|
|
139
|
+
entry_ids,
|
|
140
|
+
representation,
|
|
141
|
+
selected.backend.descriptor.coordinate,
|
|
142
|
+
fallback_reason,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _validate_request(question: str, limit: int) -> None:
|
|
147
|
+
if type(limit) is not int or not 1 <= limit <= MAX_RETRIEVAL_LIMIT:
|
|
148
|
+
raise SourceContractError(
|
|
149
|
+
"RETRIEVAL_LIMIT",
|
|
150
|
+
"limit",
|
|
151
|
+
f"must be an integer in [1, {MAX_RETRIEVAL_LIMIT}]",
|
|
152
|
+
)
|
|
153
|
+
if not isinstance(question, str):
|
|
154
|
+
raise SourceContractError("TYPE", "question", "must be a string")
|
|
155
|
+
if len(question) > MAX_QUERY_CHARS:
|
|
156
|
+
raise SourceContractError(
|
|
157
|
+
"RETRIEVAL_QUERY_LIMIT",
|
|
158
|
+
"question",
|
|
159
|
+
f"a question may not exceed {MAX_QUERY_CHARS} characters",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _rank_loaded_entry_ids(
|
|
164
|
+
representation: LoadedRetrievalRepresentation,
|
|
165
|
+
question: str,
|
|
166
|
+
*,
|
|
167
|
+
limit: int,
|
|
168
|
+
) -> tuple[str, ...]:
|
|
169
|
+
if not any(character.isalnum() for character in question):
|
|
170
|
+
return ()
|
|
171
|
+
asked = representation.backend.encode(question)
|
|
172
|
+
dimensions = representation.backend.descriptor.dimensions
|
|
173
|
+
if (
|
|
174
|
+
not isinstance(asked, tuple)
|
|
175
|
+
or len(asked) != dimensions
|
|
176
|
+
or any(type(value) is not int for value in asked)
|
|
177
|
+
):
|
|
178
|
+
raise SourceContractError(
|
|
179
|
+
"RETRIEVAL_QUERY_VECTOR",
|
|
180
|
+
"question",
|
|
181
|
+
"the selected backend returned a vector outside its exact integer coordinate",
|
|
182
|
+
)
|
|
183
|
+
if not any(asked):
|
|
184
|
+
return ()
|
|
185
|
+
|
|
186
|
+
ordered: list[tuple[int, str]] = []
|
|
187
|
+
for entry in representation.entries:
|
|
188
|
+
best = _best_loaded_layer_score(representation, entry.entry_id, asked)
|
|
189
|
+
if best > 0:
|
|
190
|
+
ordered.append((-best, entry.entry_id))
|
|
191
|
+
ordered.sort()
|
|
192
|
+
return tuple(entry_id for _negated, entry_id in ordered[:limit])
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _best_layer_score(index: SealedEmbeddingIndex, entry_id: str, asked: tuple[int, ...]) -> int:
|
|
196
|
+
"""The strongest of this entry's four layers against the question. Private on purpose.
|
|
197
|
+
|
|
198
|
+
An entry that matches only on its published column names still ranks by that layer, which is
|
|
199
|
+
what makes the four-layer split worth having.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
best = 0
|
|
203
|
+
for layer in EMBEDDING_LAYERS:
|
|
204
|
+
sealed = index.vectors.get((entry_id, layer))
|
|
205
|
+
if sealed is None:
|
|
206
|
+
continue
|
|
207
|
+
total = 0
|
|
208
|
+
for left, right in zip(asked, sealed, strict=True):
|
|
209
|
+
total += left * right
|
|
210
|
+
best = max(best, total)
|
|
211
|
+
return best
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _best_loaded_layer_score(
|
|
215
|
+
representation: LoadedRetrievalRepresentation,
|
|
216
|
+
entry_id: str,
|
|
217
|
+
asked: tuple[int, ...],
|
|
218
|
+
) -> int:
|
|
219
|
+
"""Return one private v2 ranking number; callers can receive only identities."""
|
|
220
|
+
|
|
221
|
+
best = 0
|
|
222
|
+
for layer in EMBEDDING_LAYERS:
|
|
223
|
+
sealed = representation.vectors.get((entry_id, layer))
|
|
224
|
+
if sealed is None:
|
|
225
|
+
continue
|
|
226
|
+
total = 0
|
|
227
|
+
for left, right in zip(asked, sealed, strict=True):
|
|
228
|
+
total += left * right
|
|
229
|
+
best = max(best, total)
|
|
230
|
+
return best
|