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,995 @@
|
|
|
1
|
+
"""Strict v2 envelope for independently sealed catalog retrieval representations.
|
|
2
|
+
|
|
3
|
+
The ``harness-source-catalog-index.v1`` artifact remains the representation format.
|
|
4
|
+
This module adds an outer manifest; it does not reinterpret the v1 backend coordinate or its
|
|
5
|
+
``query_safe`` flag. A lexical representation is always present. A neural representation is
|
|
6
|
+
optional and, when present, carries the exact explicit local encoder and model-artifact closure
|
|
7
|
+
that the v2 query path is allowed to resolve.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import stat
|
|
15
|
+
import struct
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from mostlyright.data_harness.canonical import (
|
|
21
|
+
CanonicalJSONError,
|
|
22
|
+
canonical_json_bytes,
|
|
23
|
+
parse_canonical_json,
|
|
24
|
+
sha256_bytes,
|
|
25
|
+
)
|
|
26
|
+
from mostlyright.data_harness.sources.catalog.contracts import (
|
|
27
|
+
CATALOG_INDEX_CONTRACT_VERSION,
|
|
28
|
+
EMBEDDING_LAYERS,
|
|
29
|
+
CatalogEntry,
|
|
30
|
+
catalog_entry_from_dict,
|
|
31
|
+
)
|
|
32
|
+
from mostlyright.data_harness.sources.catalog.embedding import (
|
|
33
|
+
BackendDescriptor,
|
|
34
|
+
EmbeddingBackend,
|
|
35
|
+
LexicalHashingBackend,
|
|
36
|
+
PinnedLocalBackendDescriptor,
|
|
37
|
+
)
|
|
38
|
+
from mostlyright.data_harness.sources.contracts import SourceContractError
|
|
39
|
+
|
|
40
|
+
RETRIEVAL_MANIFEST_SCHEMA = "harness-source-catalog-retrieval.v2"
|
|
41
|
+
LEXICAL_REPRESENTATION = "lexical"
|
|
42
|
+
NEURAL_REPRESENTATION = "neural"
|
|
43
|
+
LEXICAL_FALLBACK_REASONS = frozenset(
|
|
44
|
+
{
|
|
45
|
+
"NEURAL_NOT_REQUESTED",
|
|
46
|
+
"NEURAL_RUNTIME_UNAVAILABLE",
|
|
47
|
+
"NEURAL_TUPLE_UNAVAILABLE",
|
|
48
|
+
"NEURAL_MODEL_UNAVAILABLE",
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
MAX_REPRESENTATION_MEMBERS = 40_002
|
|
52
|
+
MAX_REPRESENTATION_MEMBER_BYTES = 128 * 1024 * 1024
|
|
53
|
+
RETRIEVAL_MANIFEST_FILENAME = "retrieval.json"
|
|
54
|
+
MAX_RETRIEVAL_MANIFEST_BYTES = 4 * 1024 * 1024
|
|
55
|
+
REPRESENTATION_DESCRIPTOR_FILENAME = "descriptor.json"
|
|
56
|
+
REPRESENTATION_DESCRIPTOR_SCHEMA = "harness-source-catalog-representation-descriptor.v1"
|
|
57
|
+
MAX_REPRESENTATION_DESCRIPTOR_BYTES = 1_024
|
|
58
|
+
|
|
59
|
+
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
|
|
60
|
+
_IDENTIFIER = re.compile(r"^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$")
|
|
61
|
+
_SEMVER = re.compile(r"^[1-9][0-9]*\.[0-9]+\.[0-9]+$")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class RetrievalManifestError(SourceContractError):
|
|
65
|
+
"""A stable refusal for an invalid or ambiguous v2 retrieval manifest."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class RepresentationMember:
|
|
70
|
+
"""One exact regular-file member in a representation closure."""
|
|
71
|
+
|
|
72
|
+
path: str
|
|
73
|
+
size: int
|
|
74
|
+
sha256: str
|
|
75
|
+
|
|
76
|
+
def __post_init__(self) -> None:
|
|
77
|
+
if (
|
|
78
|
+
not isinstance(self.path, str)
|
|
79
|
+
or not self.path
|
|
80
|
+
or self.path.startswith("/")
|
|
81
|
+
or "\\" in self.path
|
|
82
|
+
or any(part in {"", ".", ".."} for part in self.path.split("/"))
|
|
83
|
+
):
|
|
84
|
+
raise RetrievalManifestError(
|
|
85
|
+
"RETRIEVAL_MANIFEST_MEMBER",
|
|
86
|
+
"retrieval.representations.members.path",
|
|
87
|
+
"must be a canonical relative POSIX path",
|
|
88
|
+
)
|
|
89
|
+
if type(self.size) is not int or not 0 <= self.size <= MAX_REPRESENTATION_MEMBER_BYTES:
|
|
90
|
+
raise RetrievalManifestError(
|
|
91
|
+
"RETRIEVAL_MANIFEST_MEMBER",
|
|
92
|
+
"retrieval.representations.members.size",
|
|
93
|
+
f"must be an integer in [0, {MAX_REPRESENTATION_MEMBER_BYTES}]",
|
|
94
|
+
)
|
|
95
|
+
_require_digest(
|
|
96
|
+
self.sha256,
|
|
97
|
+
"retrieval.representations.members.sha256",
|
|
98
|
+
"RETRIEVAL_MANIFEST_MEMBER",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def to_dict(self) -> dict[str, Any]:
|
|
102
|
+
return {"path": self.path, "sha256": self.sha256, "size": self.size}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(frozen=True)
|
|
106
|
+
class RetrievalRepresentation:
|
|
107
|
+
"""The exact schema, backend, encoder, and member closure for one representation."""
|
|
108
|
+
|
|
109
|
+
name: str
|
|
110
|
+
schema_version: str
|
|
111
|
+
backend_id: str
|
|
112
|
+
backend_version: str
|
|
113
|
+
dimensions: int
|
|
114
|
+
quantization: str
|
|
115
|
+
query_safe: bool
|
|
116
|
+
backend_coordinate: str
|
|
117
|
+
query_encoder: str
|
|
118
|
+
model_pin_sha256: str | None
|
|
119
|
+
model_artifacts: tuple[tuple[str, str], ...]
|
|
120
|
+
members: tuple[RepresentationMember, ...]
|
|
121
|
+
artifact_sha256: str
|
|
122
|
+
|
|
123
|
+
def __post_init__(self) -> None:
|
|
124
|
+
if self.name not in {LEXICAL_REPRESENTATION, NEURAL_REPRESENTATION}:
|
|
125
|
+
raise RetrievalManifestError(
|
|
126
|
+
"RETRIEVAL_MANIFEST_REPRESENTATIONS",
|
|
127
|
+
"retrieval.representations.name",
|
|
128
|
+
"must be lexical or neural",
|
|
129
|
+
)
|
|
130
|
+
if self.schema_version != CATALOG_INDEX_CONTRACT_VERSION:
|
|
131
|
+
raise RetrievalManifestError(
|
|
132
|
+
"RETRIEVAL_MANIFEST_SCHEMA",
|
|
133
|
+
f"retrieval.representations.{self.name}.schema_version",
|
|
134
|
+
f"must equal {CATALOG_INDEX_CONTRACT_VERSION!r}",
|
|
135
|
+
)
|
|
136
|
+
if not isinstance(self.backend_id, str) or _IDENTIFIER.fullmatch(self.backend_id) is None:
|
|
137
|
+
self._backend_error("backend_id is invalid")
|
|
138
|
+
if (
|
|
139
|
+
not isinstance(self.backend_version, str)
|
|
140
|
+
or _SEMVER.fullmatch(self.backend_version) is None
|
|
141
|
+
):
|
|
142
|
+
self._backend_error("backend_version is invalid")
|
|
143
|
+
if type(self.dimensions) is not int or not 1 <= self.dimensions <= 4_096:
|
|
144
|
+
self._backend_error("dimensions are invalid")
|
|
145
|
+
if not isinstance(self.quantization, str) or not self.quantization:
|
|
146
|
+
self._backend_error("quantization is invalid")
|
|
147
|
+
if type(self.query_safe) is not bool:
|
|
148
|
+
self._backend_error("query_safe must be a boolean")
|
|
149
|
+
base_coordinate = f"{self.backend_id}@{self.backend_version}"
|
|
150
|
+
if self.name == LEXICAL_REPRESENTATION:
|
|
151
|
+
lexical = LexicalHashingBackend().descriptor
|
|
152
|
+
if (
|
|
153
|
+
not self.query_safe
|
|
154
|
+
or self.backend_coordinate != base_coordinate
|
|
155
|
+
or (
|
|
156
|
+
self.backend_id,
|
|
157
|
+
self.backend_version,
|
|
158
|
+
self.dimensions,
|
|
159
|
+
self.quantization,
|
|
160
|
+
self.query_safe,
|
|
161
|
+
)
|
|
162
|
+
!= (
|
|
163
|
+
lexical.backend_id,
|
|
164
|
+
lexical.backend_version,
|
|
165
|
+
lexical.dimensions,
|
|
166
|
+
lexical.quantization,
|
|
167
|
+
lexical.query_safe,
|
|
168
|
+
)
|
|
169
|
+
):
|
|
170
|
+
self._backend_error("lexical must retain its exact query-safe v1 coordinate")
|
|
171
|
+
if (
|
|
172
|
+
self.query_encoder != base_coordinate
|
|
173
|
+
or self.model_pin_sha256 is not None
|
|
174
|
+
or self.model_artifacts
|
|
175
|
+
):
|
|
176
|
+
raise RetrievalManifestError(
|
|
177
|
+
"RETRIEVAL_MANIFEST_LEXICAL",
|
|
178
|
+
"retrieval.representations.lexical",
|
|
179
|
+
"lexical must use its v1 backend and must not carry model artifacts",
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
if self.query_safe or not self.backend_coordinate.startswith(f"{base_coordinate}#"):
|
|
183
|
+
self._backend_error(
|
|
184
|
+
"neural must retain query_safe=false and use an explicit v2 coordinate"
|
|
185
|
+
)
|
|
186
|
+
if self.query_encoder != self.backend_coordinate:
|
|
187
|
+
raise RetrievalManifestError(
|
|
188
|
+
"RETRIEVAL_MANIFEST_NEURAL",
|
|
189
|
+
"retrieval.representations.neural.query_encoder",
|
|
190
|
+
"must name the exact governed local encoder coordinate",
|
|
191
|
+
)
|
|
192
|
+
_require_digest(
|
|
193
|
+
self.model_pin_sha256,
|
|
194
|
+
"retrieval.representations.neural.model_pin_sha256",
|
|
195
|
+
"RETRIEVAL_MANIFEST_NEURAL",
|
|
196
|
+
)
|
|
197
|
+
_validate_model_artifacts(self.model_artifacts)
|
|
198
|
+
|
|
199
|
+
if (
|
|
200
|
+
not isinstance(self.members, tuple)
|
|
201
|
+
or not self.members
|
|
202
|
+
or len(self.members) > MAX_REPRESENTATION_MEMBERS
|
|
203
|
+
or any(type(member) is not RepresentationMember for member in self.members)
|
|
204
|
+
):
|
|
205
|
+
raise RetrievalManifestError(
|
|
206
|
+
"RETRIEVAL_MANIFEST_MEMBER",
|
|
207
|
+
f"retrieval.representations.{self.name}.members",
|
|
208
|
+
"must be a non-empty bounded tuple of strict members",
|
|
209
|
+
)
|
|
210
|
+
paths = tuple(member.path for member in self.members)
|
|
211
|
+
if paths != tuple(sorted(paths)) or len(set(paths)) != len(paths):
|
|
212
|
+
raise RetrievalManifestError(
|
|
213
|
+
"RETRIEVAL_MANIFEST_MEMBER",
|
|
214
|
+
f"retrieval.representations.{self.name}.members",
|
|
215
|
+
"must be path-sorted without duplicates",
|
|
216
|
+
)
|
|
217
|
+
prefix = f"{self.name}/"
|
|
218
|
+
catalog_path = next((path for path in paths if path.endswith("/catalog.json")), "")
|
|
219
|
+
expected_descriptor_path = (
|
|
220
|
+
f"{catalog_path[: -len('catalog.json')]}{REPRESENTATION_DESCRIPTOR_FILENAME}"
|
|
221
|
+
)
|
|
222
|
+
if (
|
|
223
|
+
any(not path.startswith(prefix) for path in paths)
|
|
224
|
+
or sum(path.endswith("/catalog.json") for path in paths) != 1
|
|
225
|
+
or sum(path.endswith(f"/{REPRESENTATION_DESCRIPTOR_FILENAME}") for path in paths) != 1
|
|
226
|
+
or expected_descriptor_path not in paths
|
|
227
|
+
):
|
|
228
|
+
raise RetrievalManifestError(
|
|
229
|
+
"RETRIEVAL_MANIFEST_MEMBER",
|
|
230
|
+
f"retrieval.representations.{self.name}.members",
|
|
231
|
+
"members may bind only their representation and must include catalog.json and "
|
|
232
|
+
"descriptor.json once",
|
|
233
|
+
)
|
|
234
|
+
expected_artifact = sha256_bytes(
|
|
235
|
+
canonical_json_bytes([member.to_dict() for member in self.members])
|
|
236
|
+
)
|
|
237
|
+
if self.artifact_sha256 != expected_artifact:
|
|
238
|
+
raise RetrievalManifestError(
|
|
239
|
+
"RETRIEVAL_MANIFEST_ARTIFACT",
|
|
240
|
+
f"retrieval.representations.{self.name}.artifact_sha256",
|
|
241
|
+
"does not bind the exact sorted member closure",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
@classmethod
|
|
245
|
+
def create(
|
|
246
|
+
cls,
|
|
247
|
+
*,
|
|
248
|
+
name: str,
|
|
249
|
+
descriptor: BackendDescriptor,
|
|
250
|
+
members: tuple[RepresentationMember, ...],
|
|
251
|
+
) -> RetrievalRepresentation:
|
|
252
|
+
if not isinstance(descriptor, BackendDescriptor):
|
|
253
|
+
raise RetrievalManifestError(
|
|
254
|
+
"RETRIEVAL_MANIFEST_BACKEND",
|
|
255
|
+
f"retrieval.representations.{name}.backend",
|
|
256
|
+
"must use a strict backend descriptor",
|
|
257
|
+
)
|
|
258
|
+
ordered = tuple(sorted(members, key=lambda member: member.path))
|
|
259
|
+
model_artifacts: tuple[tuple[str, str], ...] = ()
|
|
260
|
+
model_pin_sha256: str | None = None
|
|
261
|
+
coordinate = descriptor.coordinate
|
|
262
|
+
if isinstance(descriptor, PinnedLocalBackendDescriptor):
|
|
263
|
+
model_pin_sha256 = descriptor.pin_sha256
|
|
264
|
+
model_artifacts = descriptor.artifact_sha256s
|
|
265
|
+
return cls(
|
|
266
|
+
name=name,
|
|
267
|
+
schema_version=CATALOG_INDEX_CONTRACT_VERSION,
|
|
268
|
+
backend_id=descriptor.backend_id,
|
|
269
|
+
backend_version=descriptor.backend_version,
|
|
270
|
+
dimensions=descriptor.dimensions,
|
|
271
|
+
quantization=descriptor.quantization,
|
|
272
|
+
query_safe=descriptor.query_safe,
|
|
273
|
+
backend_coordinate=coordinate,
|
|
274
|
+
query_encoder=coordinate,
|
|
275
|
+
model_pin_sha256=model_pin_sha256,
|
|
276
|
+
model_artifacts=model_artifacts,
|
|
277
|
+
members=ordered,
|
|
278
|
+
artifact_sha256=sha256_bytes(
|
|
279
|
+
canonical_json_bytes([member.to_dict() for member in ordered])
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def _backend_error(self, detail: str) -> None:
|
|
284
|
+
raise RetrievalManifestError(
|
|
285
|
+
"RETRIEVAL_MANIFEST_BACKEND",
|
|
286
|
+
f"retrieval.representations.{self.name}.backend",
|
|
287
|
+
detail,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
def to_dict(self) -> dict[str, Any]:
|
|
291
|
+
return {
|
|
292
|
+
"artifact_sha256": self.artifact_sha256,
|
|
293
|
+
"backend": {
|
|
294
|
+
"backend_coordinate": self.backend_coordinate,
|
|
295
|
+
"backend_id": self.backend_id,
|
|
296
|
+
"backend_version": self.backend_version,
|
|
297
|
+
"dimensions": self.dimensions,
|
|
298
|
+
"quantization": self.quantization,
|
|
299
|
+
"query_safe": self.query_safe,
|
|
300
|
+
},
|
|
301
|
+
"members": [member.to_dict() for member in self.members],
|
|
302
|
+
"model_pin_sha256": self.model_pin_sha256,
|
|
303
|
+
"model_artifacts": [
|
|
304
|
+
{"path": path, "sha256": digest} for path, digest in self.model_artifacts
|
|
305
|
+
],
|
|
306
|
+
"name": self.name,
|
|
307
|
+
"query_encoder": self.query_encoder,
|
|
308
|
+
"schema_version": self.schema_version,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@dataclass(frozen=True)
|
|
313
|
+
class RetrievalManifestV2:
|
|
314
|
+
"""One lexical representation and one optional neural representation over shared facts."""
|
|
315
|
+
|
|
316
|
+
shared_facts_sha256: str
|
|
317
|
+
representations: tuple[RetrievalRepresentation, ...]
|
|
318
|
+
fallback_reason: str | None = None
|
|
319
|
+
schema_version: str = RETRIEVAL_MANIFEST_SCHEMA
|
|
320
|
+
|
|
321
|
+
def __post_init__(self) -> None:
|
|
322
|
+
if self.schema_version != RETRIEVAL_MANIFEST_SCHEMA:
|
|
323
|
+
raise RetrievalManifestError(
|
|
324
|
+
"RETRIEVAL_MANIFEST_VERSION",
|
|
325
|
+
"retrieval.schema_version",
|
|
326
|
+
f"must equal {RETRIEVAL_MANIFEST_SCHEMA!r}",
|
|
327
|
+
)
|
|
328
|
+
_require_digest(
|
|
329
|
+
self.shared_facts_sha256,
|
|
330
|
+
"retrieval.shared_facts_sha256",
|
|
331
|
+
"RETRIEVAL_MANIFEST_FACTS",
|
|
332
|
+
)
|
|
333
|
+
if (
|
|
334
|
+
not isinstance(self.representations, tuple)
|
|
335
|
+
or any(type(item) is not RetrievalRepresentation for item in self.representations)
|
|
336
|
+
or tuple(item.name for item in self.representations)
|
|
337
|
+
not in {
|
|
338
|
+
(LEXICAL_REPRESENTATION,),
|
|
339
|
+
(LEXICAL_REPRESENTATION, NEURAL_REPRESENTATION),
|
|
340
|
+
}
|
|
341
|
+
):
|
|
342
|
+
raise RetrievalManifestError(
|
|
343
|
+
"RETRIEVAL_MANIFEST_REPRESENTATIONS",
|
|
344
|
+
"retrieval.representations",
|
|
345
|
+
"must contain lexical once followed by at most one neural representation",
|
|
346
|
+
)
|
|
347
|
+
if self.neural is None:
|
|
348
|
+
if self.fallback_reason not in LEXICAL_FALLBACK_REASONS:
|
|
349
|
+
raise RetrievalManifestError(
|
|
350
|
+
"RETRIEVAL_MANIFEST_FALLBACK",
|
|
351
|
+
"retrieval.fallback_reason",
|
|
352
|
+
"lexical-only v2 requires a typed neural fallback reason",
|
|
353
|
+
)
|
|
354
|
+
elif self.fallback_reason is not None:
|
|
355
|
+
raise RetrievalManifestError(
|
|
356
|
+
"RETRIEVAL_MANIFEST_FALLBACK",
|
|
357
|
+
"retrieval.fallback_reason",
|
|
358
|
+
"a hybrid manifest cannot also claim lexical-only fallback",
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def lexical(self) -> RetrievalRepresentation:
|
|
363
|
+
return self.representations[0]
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def neural(self) -> RetrievalRepresentation | None:
|
|
367
|
+
return self.representations[1] if len(self.representations) == 2 else None
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def canonical_bytes(self) -> bytes:
|
|
371
|
+
return canonical_json_bytes(self.to_dict())
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def digest(self) -> str:
|
|
375
|
+
return sha256_bytes(self.canonical_bytes)
|
|
376
|
+
|
|
377
|
+
def to_dict(self) -> dict[str, Any]:
|
|
378
|
+
return {
|
|
379
|
+
"fallback_reason": self.fallback_reason,
|
|
380
|
+
"representations": [item.to_dict() for item in self.representations],
|
|
381
|
+
"schema_version": self.schema_version,
|
|
382
|
+
"shared_facts_sha256": self.shared_facts_sha256,
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@classmethod
|
|
386
|
+
def from_bytes(cls, raw: bytes) -> RetrievalManifestV2:
|
|
387
|
+
try:
|
|
388
|
+
payload = parse_canonical_json(raw)
|
|
389
|
+
except (CanonicalJSONError, TypeError, ValueError) as error:
|
|
390
|
+
raise RetrievalManifestError(
|
|
391
|
+
"RETRIEVAL_MANIFEST_CANONICAL",
|
|
392
|
+
"retrieval",
|
|
393
|
+
"manifest bytes must be canonical JSON",
|
|
394
|
+
) from error
|
|
395
|
+
if not isinstance(payload, dict) or set(payload) != {
|
|
396
|
+
"fallback_reason",
|
|
397
|
+
"representations",
|
|
398
|
+
"schema_version",
|
|
399
|
+
"shared_facts_sha256",
|
|
400
|
+
}:
|
|
401
|
+
raise RetrievalManifestError(
|
|
402
|
+
"RETRIEVAL_MANIFEST_SHAPE",
|
|
403
|
+
"retrieval",
|
|
404
|
+
"manifest must contain the exact v2 field set",
|
|
405
|
+
)
|
|
406
|
+
raw_representations = payload["representations"]
|
|
407
|
+
if not isinstance(raw_representations, list):
|
|
408
|
+
raise RetrievalManifestError(
|
|
409
|
+
"RETRIEVAL_MANIFEST_REPRESENTATIONS",
|
|
410
|
+
"retrieval.representations",
|
|
411
|
+
"must be an array",
|
|
412
|
+
)
|
|
413
|
+
representations = tuple(
|
|
414
|
+
_representation_from_dict(item, index=index)
|
|
415
|
+
for index, item in enumerate(raw_representations)
|
|
416
|
+
)
|
|
417
|
+
try:
|
|
418
|
+
return cls(
|
|
419
|
+
schema_version=payload["schema_version"],
|
|
420
|
+
shared_facts_sha256=payload["shared_facts_sha256"],
|
|
421
|
+
fallback_reason=payload["fallback_reason"],
|
|
422
|
+
representations=representations,
|
|
423
|
+
)
|
|
424
|
+
except RetrievalManifestError:
|
|
425
|
+
raise
|
|
426
|
+
except (TypeError, ValueError) as error:
|
|
427
|
+
raise RetrievalManifestError(
|
|
428
|
+
"RETRIEVAL_MANIFEST_SHAPE", "retrieval", "manifest fields are invalid"
|
|
429
|
+
) from error
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _representation_from_dict(value: Any, *, index: int) -> RetrievalRepresentation:
|
|
433
|
+
path = f"retrieval.representations[{index}]"
|
|
434
|
+
exact = {
|
|
435
|
+
"artifact_sha256",
|
|
436
|
+
"backend",
|
|
437
|
+
"members",
|
|
438
|
+
"model_artifacts",
|
|
439
|
+
"model_pin_sha256",
|
|
440
|
+
"name",
|
|
441
|
+
"query_encoder",
|
|
442
|
+
"schema_version",
|
|
443
|
+
}
|
|
444
|
+
if not isinstance(value, dict) or set(value) != exact:
|
|
445
|
+
raise RetrievalManifestError(
|
|
446
|
+
"RETRIEVAL_MANIFEST_REPRESENTATIONS", path, "representation fields differ"
|
|
447
|
+
)
|
|
448
|
+
backend = value["backend"]
|
|
449
|
+
if not isinstance(backend, dict) or set(backend) != {
|
|
450
|
+
"backend_coordinate",
|
|
451
|
+
"backend_id",
|
|
452
|
+
"backend_version",
|
|
453
|
+
"dimensions",
|
|
454
|
+
"quantization",
|
|
455
|
+
"query_safe",
|
|
456
|
+
}:
|
|
457
|
+
raise RetrievalManifestError(
|
|
458
|
+
"RETRIEVAL_MANIFEST_BACKEND", f"{path}.backend", "backend fields differ"
|
|
459
|
+
)
|
|
460
|
+
raw_members = value["members"]
|
|
461
|
+
if not isinstance(raw_members, list):
|
|
462
|
+
raise RetrievalManifestError(
|
|
463
|
+
"RETRIEVAL_MANIFEST_MEMBER", f"{path}.members", "must be an array"
|
|
464
|
+
)
|
|
465
|
+
members = tuple(_member_from_dict(item, path=f"{path}.members") for item in raw_members)
|
|
466
|
+
raw_artifacts = value["model_artifacts"]
|
|
467
|
+
if not isinstance(raw_artifacts, list):
|
|
468
|
+
raise RetrievalManifestError(
|
|
469
|
+
"RETRIEVAL_MANIFEST_NEURAL", f"{path}.model_artifacts", "must be an array"
|
|
470
|
+
)
|
|
471
|
+
artifacts: list[tuple[str, str]] = []
|
|
472
|
+
for item in raw_artifacts:
|
|
473
|
+
if not isinstance(item, dict) or set(item) != {"path", "sha256"}:
|
|
474
|
+
raise RetrievalManifestError(
|
|
475
|
+
"RETRIEVAL_MANIFEST_NEURAL",
|
|
476
|
+
f"{path}.model_artifacts",
|
|
477
|
+
"artifact fields differ",
|
|
478
|
+
)
|
|
479
|
+
artifacts.append((item["path"], item["sha256"]))
|
|
480
|
+
return RetrievalRepresentation(
|
|
481
|
+
name=value["name"],
|
|
482
|
+
schema_version=value["schema_version"],
|
|
483
|
+
backend_id=backend["backend_id"],
|
|
484
|
+
backend_version=backend["backend_version"],
|
|
485
|
+
dimensions=backend["dimensions"],
|
|
486
|
+
quantization=backend["quantization"],
|
|
487
|
+
query_safe=backend["query_safe"],
|
|
488
|
+
backend_coordinate=backend["backend_coordinate"],
|
|
489
|
+
query_encoder=value["query_encoder"],
|
|
490
|
+
model_pin_sha256=value["model_pin_sha256"],
|
|
491
|
+
model_artifacts=tuple(artifacts),
|
|
492
|
+
members=members,
|
|
493
|
+
artifact_sha256=value["artifact_sha256"],
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _member_from_dict(value: Any, *, path: str) -> RepresentationMember:
|
|
498
|
+
if not isinstance(value, dict) or set(value) != {"path", "sha256", "size"}:
|
|
499
|
+
raise RetrievalManifestError("RETRIEVAL_MANIFEST_MEMBER", path, "member fields differ")
|
|
500
|
+
return RepresentationMember(path=value["path"], size=value["size"], sha256=value["sha256"])
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _validate_model_artifacts(artifacts: tuple[tuple[str, str], ...]) -> None:
|
|
504
|
+
if not artifacts or tuple(sorted(artifacts)) != artifacts:
|
|
505
|
+
raise RetrievalManifestError(
|
|
506
|
+
"RETRIEVAL_MANIFEST_NEURAL",
|
|
507
|
+
"retrieval.representations.neural.model_artifacts",
|
|
508
|
+
"must be a non-empty path-sorted artifact closure",
|
|
509
|
+
)
|
|
510
|
+
for path, digest in artifacts:
|
|
511
|
+
if (
|
|
512
|
+
not isinstance(path, str)
|
|
513
|
+
or not path
|
|
514
|
+
or "/" in path
|
|
515
|
+
or "\\" in path
|
|
516
|
+
or _SHA256.fullmatch(digest) is None
|
|
517
|
+
):
|
|
518
|
+
raise RetrievalManifestError(
|
|
519
|
+
"RETRIEVAL_MANIFEST_NEURAL",
|
|
520
|
+
"retrieval.representations.neural.model_artifacts",
|
|
521
|
+
"must contain safe names and lowercase SHA-256 digests",
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _require_digest(value: Any, path: str, code: str) -> None:
|
|
526
|
+
if not isinstance(value, str) or _SHA256.fullmatch(value) is None:
|
|
527
|
+
raise RetrievalManifestError(code, path, "must be a lowercase SHA-256 digest")
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def backend_descriptor_sha256(descriptor: BackendDescriptor) -> str:
|
|
531
|
+
"""Digest the complete backend identity sealed into each v2 generation."""
|
|
532
|
+
|
|
533
|
+
model_pin_sha256: str | None = None
|
|
534
|
+
model_artifacts: tuple[tuple[str, str], ...] = ()
|
|
535
|
+
if isinstance(descriptor, PinnedLocalBackendDescriptor):
|
|
536
|
+
model_pin_sha256 = descriptor.pin_sha256
|
|
537
|
+
model_artifacts = descriptor.artifact_sha256s
|
|
538
|
+
return _backend_identity_sha256(
|
|
539
|
+
backend_coordinate=descriptor.coordinate,
|
|
540
|
+
backend_id=descriptor.backend_id,
|
|
541
|
+
backend_version=descriptor.backend_version,
|
|
542
|
+
dimensions=descriptor.dimensions,
|
|
543
|
+
quantization=descriptor.quantization,
|
|
544
|
+
query_safe=descriptor.query_safe,
|
|
545
|
+
model_pin_sha256=model_pin_sha256,
|
|
546
|
+
model_artifacts=model_artifacts,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def backend_descriptor_bytes(descriptor: BackendDescriptor) -> bytes:
|
|
551
|
+
"""Canonical authenticated v2 generation metadata for one strict descriptor."""
|
|
552
|
+
|
|
553
|
+
return _descriptor_metadata_bytes(backend_descriptor_sha256(descriptor))
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def _backend_identity_sha256(
|
|
557
|
+
*,
|
|
558
|
+
backend_coordinate: str,
|
|
559
|
+
backend_id: str,
|
|
560
|
+
backend_version: str,
|
|
561
|
+
dimensions: int,
|
|
562
|
+
quantization: str,
|
|
563
|
+
query_safe: bool,
|
|
564
|
+
model_pin_sha256: str | None,
|
|
565
|
+
model_artifacts: tuple[tuple[str, str], ...],
|
|
566
|
+
) -> str:
|
|
567
|
+
return sha256_bytes(
|
|
568
|
+
canonical_json_bytes(
|
|
569
|
+
{
|
|
570
|
+
"backend_coordinate": backend_coordinate,
|
|
571
|
+
"backend_id": backend_id,
|
|
572
|
+
"backend_version": backend_version,
|
|
573
|
+
"dimensions": dimensions,
|
|
574
|
+
"model_artifacts": [
|
|
575
|
+
{"path": path, "sha256": digest} for path, digest in model_artifacts
|
|
576
|
+
],
|
|
577
|
+
"model_pin_sha256": model_pin_sha256,
|
|
578
|
+
"quantization": quantization,
|
|
579
|
+
"query_safe": query_safe,
|
|
580
|
+
}
|
|
581
|
+
)
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def _descriptor_metadata_bytes(descriptor_sha256: str) -> bytes:
|
|
586
|
+
return canonical_json_bytes(
|
|
587
|
+
{
|
|
588
|
+
"backend_descriptor_sha256": descriptor_sha256,
|
|
589
|
+
"schema_version": REPRESENTATION_DESCRIPTOR_SCHEMA,
|
|
590
|
+
}
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _representation_descriptor_bytes(representation: RetrievalRepresentation) -> bytes:
|
|
595
|
+
return _descriptor_metadata_bytes(
|
|
596
|
+
_backend_identity_sha256(
|
|
597
|
+
backend_coordinate=representation.backend_coordinate,
|
|
598
|
+
backend_id=representation.backend_id,
|
|
599
|
+
backend_version=representation.backend_version,
|
|
600
|
+
dimensions=representation.dimensions,
|
|
601
|
+
quantization=representation.quantization,
|
|
602
|
+
query_safe=representation.query_safe,
|
|
603
|
+
model_pin_sha256=representation.model_pin_sha256,
|
|
604
|
+
model_artifacts=representation.model_artifacts,
|
|
605
|
+
)
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
@dataclass(frozen=True)
|
|
610
|
+
class LoadedRetrievalRepresentation:
|
|
611
|
+
"""A fully verified sealed representation and its explicitly supplied encoder."""
|
|
612
|
+
|
|
613
|
+
entries: tuple[CatalogEntry, ...]
|
|
614
|
+
vectors: dict[tuple[str, str], tuple[int, ...]]
|
|
615
|
+
backend: EmbeddingBackend
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
@dataclass(frozen=True)
|
|
619
|
+
class _VerifiedRetrievalRepresentation:
|
|
620
|
+
"""One representation whose inner manifest and vector closure have been authenticated."""
|
|
621
|
+
|
|
622
|
+
entries: tuple[CatalogEntry, ...]
|
|
623
|
+
vectors: dict[tuple[str, str], tuple[int, ...]]
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
@dataclass(frozen=True)
|
|
627
|
+
class LoadedRetrievalCatalog:
|
|
628
|
+
"""The verified v2 envelope, with an optional locally resolved neural representation."""
|
|
629
|
+
|
|
630
|
+
manifest: RetrievalManifestV2
|
|
631
|
+
lexical: LoadedRetrievalRepresentation
|
|
632
|
+
neural: LoadedRetrievalRepresentation | None
|
|
633
|
+
fallback_reason: str | None
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def load_retrieval_catalog(
|
|
637
|
+
directory: Path,
|
|
638
|
+
*,
|
|
639
|
+
expected_manifest_sha256: str,
|
|
640
|
+
neural_backend: EmbeddingBackend | None = None,
|
|
641
|
+
) -> LoadedRetrievalCatalog:
|
|
642
|
+
"""Verify the complete outer closure before exposing either representation.
|
|
643
|
+
|
|
644
|
+
Neural resolution is deliberately explicit. Omitting the exact local backend never causes
|
|
645
|
+
ambient model discovery: a hybrid artifact then opens as a typed lexical-only runtime view.
|
|
646
|
+
"""
|
|
647
|
+
|
|
648
|
+
if not isinstance(directory, Path):
|
|
649
|
+
raise RetrievalManifestError("TYPE", "directory", "must be a Path")
|
|
650
|
+
_require_digest(
|
|
651
|
+
expected_manifest_sha256,
|
|
652
|
+
"retrieval.expected_manifest_sha256",
|
|
653
|
+
"RETRIEVAL_MANIFEST_DIGEST",
|
|
654
|
+
)
|
|
655
|
+
root_fd = _open_directory(directory, label="retrieval root")
|
|
656
|
+
try:
|
|
657
|
+
raw_manifest = _read_regular(
|
|
658
|
+
root_fd,
|
|
659
|
+
(RETRIEVAL_MANIFEST_FILENAME,),
|
|
660
|
+
maximum=MAX_RETRIEVAL_MANIFEST_BYTES,
|
|
661
|
+
)
|
|
662
|
+
if sha256_bytes(raw_manifest) != expected_manifest_sha256:
|
|
663
|
+
raise RetrievalManifestError(
|
|
664
|
+
"RETRIEVAL_MANIFEST_DIGEST",
|
|
665
|
+
RETRIEVAL_MANIFEST_FILENAME,
|
|
666
|
+
"published manifest differs from the required digest",
|
|
667
|
+
)
|
|
668
|
+
manifest = RetrievalManifestV2.from_bytes(raw_manifest)
|
|
669
|
+
captured: dict[str, bytes] = {}
|
|
670
|
+
for representation in manifest.representations:
|
|
671
|
+
for member in representation.members:
|
|
672
|
+
raw = _read_regular(
|
|
673
|
+
root_fd,
|
|
674
|
+
tuple(member.path.split("/")),
|
|
675
|
+
maximum=member.size,
|
|
676
|
+
)
|
|
677
|
+
if len(raw) != member.size or sha256_bytes(raw) != member.sha256:
|
|
678
|
+
raise RetrievalManifestError(
|
|
679
|
+
"RETRIEVAL_MEMBER_TAMPER",
|
|
680
|
+
member.path,
|
|
681
|
+
"member size or digest differs from the outer manifest",
|
|
682
|
+
)
|
|
683
|
+
captured[member.path] = raw
|
|
684
|
+
|
|
685
|
+
verified_lexical = _verify_representation(manifest.lexical, captured=captured)
|
|
686
|
+
verified_neural = (
|
|
687
|
+
None
|
|
688
|
+
if manifest.neural is None
|
|
689
|
+
else _verify_representation(manifest.neural, captured=captured)
|
|
690
|
+
)
|
|
691
|
+
_require_shared_facts(manifest, verified_lexical, verified_neural)
|
|
692
|
+
|
|
693
|
+
lexical = _bind_representation_backend(
|
|
694
|
+
manifest.lexical,
|
|
695
|
+
verified=verified_lexical,
|
|
696
|
+
backend=LexicalHashingBackend(),
|
|
697
|
+
)
|
|
698
|
+
neural: LoadedRetrievalRepresentation | None = None
|
|
699
|
+
fallback_reason = manifest.fallback_reason
|
|
700
|
+
if manifest.neural is not None and neural_backend is not None:
|
|
701
|
+
assert verified_neural is not None
|
|
702
|
+
neural = _bind_representation_backend(
|
|
703
|
+
manifest.neural,
|
|
704
|
+
verified=verified_neural,
|
|
705
|
+
backend=neural_backend,
|
|
706
|
+
)
|
|
707
|
+
elif manifest.neural is not None:
|
|
708
|
+
fallback_reason = "NEURAL_RUNTIME_UNAVAILABLE"
|
|
709
|
+
|
|
710
|
+
return LoadedRetrievalCatalog(
|
|
711
|
+
manifest=manifest,
|
|
712
|
+
lexical=lexical,
|
|
713
|
+
neural=neural,
|
|
714
|
+
fallback_reason=fallback_reason,
|
|
715
|
+
)
|
|
716
|
+
finally:
|
|
717
|
+
os.close(root_fd)
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def _bind_representation_backend(
|
|
721
|
+
representation: RetrievalRepresentation,
|
|
722
|
+
*,
|
|
723
|
+
verified: _VerifiedRetrievalRepresentation,
|
|
724
|
+
backend: EmbeddingBackend,
|
|
725
|
+
) -> LoadedRetrievalRepresentation:
|
|
726
|
+
descriptor = backend.descriptor
|
|
727
|
+
expected_descriptor = (
|
|
728
|
+
representation.backend_id,
|
|
729
|
+
representation.backend_version,
|
|
730
|
+
representation.dimensions,
|
|
731
|
+
representation.quantization,
|
|
732
|
+
representation.query_safe,
|
|
733
|
+
representation.backend_coordinate,
|
|
734
|
+
)
|
|
735
|
+
observed_descriptor = (
|
|
736
|
+
descriptor.backend_id,
|
|
737
|
+
descriptor.backend_version,
|
|
738
|
+
descriptor.dimensions,
|
|
739
|
+
descriptor.quantization,
|
|
740
|
+
descriptor.query_safe,
|
|
741
|
+
descriptor.coordinate,
|
|
742
|
+
)
|
|
743
|
+
if observed_descriptor != expected_descriptor:
|
|
744
|
+
raise RetrievalManifestError(
|
|
745
|
+
"RETRIEVAL_BACKEND_MISMATCH",
|
|
746
|
+
f"retrieval.representations.{representation.name}.backend",
|
|
747
|
+
"the supplied encoder does not match the exact sealed coordinate",
|
|
748
|
+
)
|
|
749
|
+
if representation.name == NEURAL_REPRESENTATION:
|
|
750
|
+
if not isinstance(descriptor, PinnedLocalBackendDescriptor):
|
|
751
|
+
raise RetrievalManifestError(
|
|
752
|
+
"RETRIEVAL_BACKEND_MISMATCH",
|
|
753
|
+
"retrieval.representations.neural.backend",
|
|
754
|
+
"neural loading requires an explicit pinned local descriptor",
|
|
755
|
+
)
|
|
756
|
+
if descriptor.artifact_sha256s != representation.model_artifacts:
|
|
757
|
+
raise RetrievalManifestError(
|
|
758
|
+
"RETRIEVAL_BACKEND_MISMATCH",
|
|
759
|
+
"retrieval.representations.neural.model_artifacts",
|
|
760
|
+
"the supplied encoder artifact closure differs",
|
|
761
|
+
)
|
|
762
|
+
if descriptor.pin_sha256 != representation.model_pin_sha256:
|
|
763
|
+
raise RetrievalManifestError(
|
|
764
|
+
"RETRIEVAL_BACKEND_MISMATCH",
|
|
765
|
+
"retrieval.representations.neural.model_pin_sha256",
|
|
766
|
+
"the supplied encoder PIN digest differs",
|
|
767
|
+
)
|
|
768
|
+
return LoadedRetrievalRepresentation(
|
|
769
|
+
entries=verified.entries,
|
|
770
|
+
vectors=verified.vectors,
|
|
771
|
+
backend=backend,
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _verify_representation(
|
|
776
|
+
representation: RetrievalRepresentation,
|
|
777
|
+
*,
|
|
778
|
+
captured: dict[str, bytes],
|
|
779
|
+
) -> _VerifiedRetrievalRepresentation:
|
|
780
|
+
|
|
781
|
+
catalog_members = [
|
|
782
|
+
item for item in representation.members if item.path.endswith("/catalog.json")
|
|
783
|
+
]
|
|
784
|
+
catalog_path = catalog_members[0].path
|
|
785
|
+
try:
|
|
786
|
+
payload = parse_canonical_json(captured[catalog_path])
|
|
787
|
+
except (CanonicalJSONError, TypeError, ValueError) as error:
|
|
788
|
+
raise RetrievalManifestError(
|
|
789
|
+
"RETRIEVAL_REPRESENTATION_MANIFEST",
|
|
790
|
+
catalog_path,
|
|
791
|
+
"inner v1 manifest is not canonical JSON",
|
|
792
|
+
) from error
|
|
793
|
+
if (
|
|
794
|
+
not isinstance(payload, dict)
|
|
795
|
+
or set(payload) != {"entries", "schema_version"}
|
|
796
|
+
or payload["schema_version"] != CATALOG_INDEX_CONTRACT_VERSION
|
|
797
|
+
or not isinstance(payload["entries"], list)
|
|
798
|
+
):
|
|
799
|
+
raise RetrievalManifestError(
|
|
800
|
+
"RETRIEVAL_REPRESENTATION_MANIFEST",
|
|
801
|
+
catalog_path,
|
|
802
|
+
"inner v1 manifest fields differ",
|
|
803
|
+
)
|
|
804
|
+
try:
|
|
805
|
+
entries = tuple(catalog_entry_from_dict(item) for item in payload["entries"])
|
|
806
|
+
except (SourceContractError, TypeError, ValueError) as error:
|
|
807
|
+
raise RetrievalManifestError(
|
|
808
|
+
"RETRIEVAL_REPRESENTATION_MANIFEST",
|
|
809
|
+
catalog_path,
|
|
810
|
+
"inner entries violate the v1 catalog contract",
|
|
811
|
+
) from error
|
|
812
|
+
if tuple(sorted(entries, key=lambda item: (item.entry_id, item.entry_version))) != entries:
|
|
813
|
+
raise RetrievalManifestError(
|
|
814
|
+
"RETRIEVAL_REPRESENTATION_MANIFEST",
|
|
815
|
+
catalog_path,
|
|
816
|
+
"inner entries are not in canonical order",
|
|
817
|
+
)
|
|
818
|
+
if len({entry.entry_id for entry in entries}) != len(entries):
|
|
819
|
+
raise RetrievalManifestError(
|
|
820
|
+
"RETRIEVAL_REPRESENTATION_MANIFEST",
|
|
821
|
+
catalog_path,
|
|
822
|
+
"inner entry ids are not unique",
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
prefix = catalog_path[: -len("catalog.json")]
|
|
826
|
+
descriptor_path = f"{prefix}{REPRESENTATION_DESCRIPTOR_FILENAME}"
|
|
827
|
+
try:
|
|
828
|
+
descriptor_raw = captured[descriptor_path]
|
|
829
|
+
except KeyError:
|
|
830
|
+
raise RetrievalManifestError(
|
|
831
|
+
"RETRIEVAL_REPRESENTATION_MEMBER",
|
|
832
|
+
descriptor_path,
|
|
833
|
+
"representation descriptor is absent from the outer closure",
|
|
834
|
+
) from None
|
|
835
|
+
if descriptor_raw != _representation_descriptor_bytes(representation):
|
|
836
|
+
raise RetrievalManifestError(
|
|
837
|
+
"RETRIEVAL_BACKEND_MISMATCH",
|
|
838
|
+
descriptor_path,
|
|
839
|
+
"generation descriptor differs from the exact outer backend identity",
|
|
840
|
+
)
|
|
841
|
+
required_paths = {catalog_path, descriptor_path}
|
|
842
|
+
vectors: dict[tuple[str, str], tuple[int, ...]] = {}
|
|
843
|
+
for entry in entries:
|
|
844
|
+
if tuple(sorted(reference.layer for reference in entry.vectors)) != tuple(
|
|
845
|
+
sorted(EMBEDDING_LAYERS)
|
|
846
|
+
):
|
|
847
|
+
raise RetrievalManifestError(
|
|
848
|
+
"RETRIEVAL_REPRESENTATION_VECTOR",
|
|
849
|
+
entry.coordinate,
|
|
850
|
+
"each entry must bind exactly one vector per layer",
|
|
851
|
+
)
|
|
852
|
+
for reference in entry.vectors:
|
|
853
|
+
if reference.entry_facts_digest != entry.facts_digest:
|
|
854
|
+
raise RetrievalManifestError(
|
|
855
|
+
"RETRIEVAL_REPRESENTATION_VECTOR",
|
|
856
|
+
f"{entry.coordinate}.{reference.layer}",
|
|
857
|
+
"vector is bound to different entry facts",
|
|
858
|
+
)
|
|
859
|
+
if (
|
|
860
|
+
reference.backend_id,
|
|
861
|
+
reference.backend_version,
|
|
862
|
+
reference.dimensions,
|
|
863
|
+
reference.quantization,
|
|
864
|
+
) != (
|
|
865
|
+
representation.backend_id,
|
|
866
|
+
representation.backend_version,
|
|
867
|
+
representation.dimensions,
|
|
868
|
+
representation.quantization,
|
|
869
|
+
):
|
|
870
|
+
raise RetrievalManifestError(
|
|
871
|
+
"RETRIEVAL_BACKEND_MISMATCH",
|
|
872
|
+
f"{entry.coordinate}.{reference.layer}",
|
|
873
|
+
"inner vector coordinate differs from the outer representation",
|
|
874
|
+
)
|
|
875
|
+
member_path = f"{prefix}vectors/{reference.vector_sha256}.vec"
|
|
876
|
+
required_paths.add(member_path)
|
|
877
|
+
try:
|
|
878
|
+
vector_raw = captured[member_path]
|
|
879
|
+
except KeyError:
|
|
880
|
+
raise RetrievalManifestError(
|
|
881
|
+
"RETRIEVAL_REPRESENTATION_MEMBER",
|
|
882
|
+
member_path,
|
|
883
|
+
"inner manifest references a member absent from the outer closure",
|
|
884
|
+
) from None
|
|
885
|
+
if len(vector_raw) != representation.dimensions * 4:
|
|
886
|
+
raise RetrievalManifestError(
|
|
887
|
+
"RETRIEVAL_REPRESENTATION_VECTOR",
|
|
888
|
+
member_path,
|
|
889
|
+
"vector byte length differs from its dimensions",
|
|
890
|
+
)
|
|
891
|
+
if sha256_bytes(vector_raw) != reference.vector_sha256:
|
|
892
|
+
raise RetrievalManifestError(
|
|
893
|
+
"RETRIEVAL_REPRESENTATION_VECTOR",
|
|
894
|
+
member_path,
|
|
895
|
+
"vector bytes differ from the digest bound by the inner manifest",
|
|
896
|
+
)
|
|
897
|
+
vectors[(entry.entry_id, reference.layer)] = struct.unpack(
|
|
898
|
+
f">{representation.dimensions}i", vector_raw
|
|
899
|
+
)
|
|
900
|
+
declared_paths = {item.path for item in representation.members}
|
|
901
|
+
if declared_paths != required_paths:
|
|
902
|
+
raise RetrievalManifestError(
|
|
903
|
+
"RETRIEVAL_REPRESENTATION_MEMBER",
|
|
904
|
+
f"retrieval.representations.{representation.name}.members",
|
|
905
|
+
"outer closure has missing or unreferenced members",
|
|
906
|
+
)
|
|
907
|
+
return _VerifiedRetrievalRepresentation(entries=entries, vectors=vectors)
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def _require_shared_facts(
|
|
911
|
+
manifest: RetrievalManifestV2,
|
|
912
|
+
lexical: _VerifiedRetrievalRepresentation,
|
|
913
|
+
neural: _VerifiedRetrievalRepresentation | None,
|
|
914
|
+
) -> None:
|
|
915
|
+
facts = tuple((entry.coordinate, entry.facts_digest) for entry in lexical.entries)
|
|
916
|
+
expected = sha256_bytes(
|
|
917
|
+
canonical_json_bytes(
|
|
918
|
+
[{"coordinate": coordinate, "facts_sha256": digest} for coordinate, digest in facts]
|
|
919
|
+
)
|
|
920
|
+
)
|
|
921
|
+
if expected != manifest.shared_facts_sha256:
|
|
922
|
+
raise RetrievalManifestError(
|
|
923
|
+
"RETRIEVAL_SHARED_FACTS",
|
|
924
|
+
"retrieval.shared_facts_sha256",
|
|
925
|
+
"lexical facts differ from the shared facts binding",
|
|
926
|
+
)
|
|
927
|
+
if neural is not None:
|
|
928
|
+
neural_facts = tuple((entry.coordinate, entry.facts_digest) for entry in neural.entries)
|
|
929
|
+
if neural_facts != facts:
|
|
930
|
+
raise RetrievalManifestError(
|
|
931
|
+
"RETRIEVAL_SHARED_FACTS",
|
|
932
|
+
"retrieval.representations.neural",
|
|
933
|
+
"neural and lexical representations do not seal identical facts",
|
|
934
|
+
)
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def _open_directory(path: Path, *, label: str, dir_fd: int | None = None) -> int:
|
|
938
|
+
flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0)
|
|
939
|
+
try:
|
|
940
|
+
descriptor = os.open(path, flags, dir_fd=dir_fd)
|
|
941
|
+
except OSError as error:
|
|
942
|
+
raise RetrievalManifestError(
|
|
943
|
+
"RETRIEVAL_MEMBER_TAMPER", label, "directory cannot be opened without following links"
|
|
944
|
+
) from error
|
|
945
|
+
info = os.fstat(descriptor)
|
|
946
|
+
if not stat.S_ISDIR(info.st_mode):
|
|
947
|
+
os.close(descriptor)
|
|
948
|
+
raise RetrievalManifestError("RETRIEVAL_MEMBER_TAMPER", label, "must be a directory")
|
|
949
|
+
return descriptor
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def _read_regular(root_fd: int, parts: tuple[str, ...], *, maximum: int) -> bytes:
|
|
953
|
+
owned: list[int] = []
|
|
954
|
+
parent_fd = root_fd
|
|
955
|
+
try:
|
|
956
|
+
for part in parts[:-1]:
|
|
957
|
+
child = _open_directory(Path(part), label="/".join(parts), dir_fd=parent_fd)
|
|
958
|
+
owned.append(child)
|
|
959
|
+
parent_fd = child
|
|
960
|
+
flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_CLOEXEC", 0)
|
|
961
|
+
try:
|
|
962
|
+
descriptor = os.open(parts[-1], flags, dir_fd=parent_fd)
|
|
963
|
+
except OSError as error:
|
|
964
|
+
raise RetrievalManifestError(
|
|
965
|
+
"RETRIEVAL_MEMBER_TAMPER", "/".join(parts), "member cannot be opened safely"
|
|
966
|
+
) from error
|
|
967
|
+
try:
|
|
968
|
+
before = os.fstat(descriptor)
|
|
969
|
+
if not stat.S_ISREG(before.st_mode) or before.st_nlink != 1 or before.st_size > maximum:
|
|
970
|
+
raise RetrievalManifestError(
|
|
971
|
+
"RETRIEVAL_MEMBER_TAMPER",
|
|
972
|
+
"/".join(parts),
|
|
973
|
+
"member is not a bounded single-link regular file",
|
|
974
|
+
)
|
|
975
|
+
raw = bytearray()
|
|
976
|
+
while len(raw) <= maximum:
|
|
977
|
+
chunk = os.read(descriptor, min(1024 * 1024, maximum + 1 - len(raw)))
|
|
978
|
+
if not chunk:
|
|
979
|
+
break
|
|
980
|
+
raw.extend(chunk)
|
|
981
|
+
after = os.fstat(descriptor)
|
|
982
|
+
if len(raw) > maximum or (before.st_dev, before.st_ino, before.st_size) != (
|
|
983
|
+
after.st_dev,
|
|
984
|
+
after.st_ino,
|
|
985
|
+
after.st_size,
|
|
986
|
+
):
|
|
987
|
+
raise RetrievalManifestError(
|
|
988
|
+
"RETRIEVAL_MEMBER_TAMPER", "/".join(parts), "member changed during read"
|
|
989
|
+
)
|
|
990
|
+
return bytes(raw)
|
|
991
|
+
finally:
|
|
992
|
+
os.close(descriptor)
|
|
993
|
+
finally:
|
|
994
|
+
for descriptor in reversed(owned):
|
|
995
|
+
os.close(descriptor)
|