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,374 @@
|
|
|
1
|
+
"""The facts gate: decide whether a catalog entry may be used, on facts alone.
|
|
2
|
+
|
|
3
|
+
Every decision here is a function of structured facts -- the rights status and its obligations,
|
|
4
|
+
whether the source needs authentication, which payload formats it publishes, how often it updates,
|
|
5
|
+
and whether it is answering. None of those is a matter of degree, and none of them is negotiable
|
|
6
|
+
by how well a source matched a question.
|
|
7
|
+
|
|
8
|
+
This module has no way to consult a similarity number. It does not import the module that
|
|
9
|
+
computes one, or any module that touches a sealed embedding; its inputs cannot represent one; and
|
|
10
|
+
its own function signatures have no parameter that could carry one. That is a structural property
|
|
11
|
+
rather than a convention, and a test proves it by reading the import closure and the syntax tree
|
|
12
|
+
rather than by reading this paragraph.
|
|
13
|
+
|
|
14
|
+
Rights are not re-adjudicated here either. ``sources.rights_rule.rights_disposition`` is the one
|
|
15
|
+
authority for what a rights status means, and ``evaluate_obligations`` is the one obligation
|
|
16
|
+
evaluator. A second copy could disagree with the first, and the disagreement
|
|
17
|
+
would be about whether someone may lawfully use a dataset.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from mostlyright.data_harness.sources.catalog.contracts import CatalogEntry
|
|
26
|
+
from mostlyright.data_harness.sources.contracts import (
|
|
27
|
+
DATA_FORMATS,
|
|
28
|
+
OBLIGATION_LIFECYCLES,
|
|
29
|
+
SourceContractError,
|
|
30
|
+
_CanonicalContract,
|
|
31
|
+
evaluate_obligations,
|
|
32
|
+
)
|
|
33
|
+
from mostlyright.data_harness.sources.rights_rule import rights_disposition
|
|
34
|
+
|
|
35
|
+
CATALOG_REQUIREMENT_CONTRACT_VERSION = "harness-source-catalog-requirement.v1"
|
|
36
|
+
CATALOG_VERDICT_CONTRACT_VERSION = "harness-source-catalog-verdict.v1"
|
|
37
|
+
|
|
38
|
+
# Ordered. A verdict's check list is digested, so its order must be fixed.
|
|
39
|
+
CATALOG_CHECKS = (
|
|
40
|
+
"rights",
|
|
41
|
+
"obligations",
|
|
42
|
+
"authentication",
|
|
43
|
+
"data_format",
|
|
44
|
+
"cadence",
|
|
45
|
+
"liveness",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
CATALOG_DISPOSITIONS = ("admitted", "refused", "human_escalation_required")
|
|
49
|
+
|
|
50
|
+
MAX_DETAIL = 500
|
|
51
|
+
MAX_REQUIRED_FORMATS = 16
|
|
52
|
+
MAX_ATTACHED_DIGESTS = 32
|
|
53
|
+
MAX_FREQUENCY_SECONDS = 10 * 365 * 24 * 60 * 60
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class CatalogRequirement(_CanonicalContract):
|
|
58
|
+
"""What a caller needs from a source, expressed entirely as facts.
|
|
59
|
+
|
|
60
|
+
There is deliberately no threshold field. A threshold would be an admission decision taken by
|
|
61
|
+
degree of textual match, which is the one thing this gate exists to make impossible.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
intended_use: str
|
|
65
|
+
lifecycle: str
|
|
66
|
+
required_data_formats: tuple[str, ...]
|
|
67
|
+
max_update_frequency_seconds: int
|
|
68
|
+
require_open_access: bool = True
|
|
69
|
+
require_live: bool = True
|
|
70
|
+
attached_license_artifact_digests: tuple[str, ...] = ()
|
|
71
|
+
schema_version: str = CATALOG_REQUIREMENT_CONTRACT_VERSION
|
|
72
|
+
|
|
73
|
+
def __post_init__(self) -> None:
|
|
74
|
+
_constant(
|
|
75
|
+
self.schema_version,
|
|
76
|
+
CATALOG_REQUIREMENT_CONTRACT_VERSION,
|
|
77
|
+
"requirement.schema_version",
|
|
78
|
+
)
|
|
79
|
+
_text(self.intended_use, "requirement.intended_use", maximum=200)
|
|
80
|
+
_choice(self.lifecycle, OBLIGATION_LIFECYCLES, "requirement.lifecycle")
|
|
81
|
+
_choices(
|
|
82
|
+
self.required_data_formats,
|
|
83
|
+
DATA_FORMATS,
|
|
84
|
+
"requirement.required_data_formats",
|
|
85
|
+
maximum=MAX_REQUIRED_FORMATS,
|
|
86
|
+
)
|
|
87
|
+
_integer(
|
|
88
|
+
self.max_update_frequency_seconds,
|
|
89
|
+
"requirement.max_update_frequency_seconds",
|
|
90
|
+
minimum=0,
|
|
91
|
+
maximum=MAX_FREQUENCY_SECONDS,
|
|
92
|
+
)
|
|
93
|
+
_boolean(self.require_open_access, "requirement.require_open_access")
|
|
94
|
+
_boolean(self.require_live, "requirement.require_live")
|
|
95
|
+
_digests(
|
|
96
|
+
self.attached_license_artifact_digests,
|
|
97
|
+
"requirement.attached_license_artifact_digests",
|
|
98
|
+
maximum=MAX_ATTACHED_DIGESTS,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def to_dict(self) -> dict[str, Any]:
|
|
102
|
+
return {
|
|
103
|
+
"schema_version": self.schema_version,
|
|
104
|
+
"intended_use": self.intended_use,
|
|
105
|
+
"lifecycle": self.lifecycle,
|
|
106
|
+
"required_data_formats": list(self.required_data_formats),
|
|
107
|
+
"max_update_frequency_seconds": self.max_update_frequency_seconds,
|
|
108
|
+
"require_open_access": self.require_open_access,
|
|
109
|
+
"require_live": self.require_live,
|
|
110
|
+
"attached_license_artifact_digests": list(self.attached_license_artifact_digests),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass(frozen=True)
|
|
115
|
+
class CatalogCheck(_CanonicalContract):
|
|
116
|
+
"""One named fact check and how it came out."""
|
|
117
|
+
|
|
118
|
+
check: str
|
|
119
|
+
outcome: str
|
|
120
|
+
detail: str
|
|
121
|
+
|
|
122
|
+
def __post_init__(self) -> None:
|
|
123
|
+
_choice(self.check, frozenset(CATALOG_CHECKS), "check.check")
|
|
124
|
+
_choice(self.outcome, frozenset({"pass", "fail"}), "check.outcome")
|
|
125
|
+
_text(self.detail, "check.detail", maximum=MAX_DETAIL)
|
|
126
|
+
|
|
127
|
+
def to_dict(self) -> dict[str, str]:
|
|
128
|
+
return {"check": self.check, "outcome": self.outcome, "detail": self.detail}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True)
|
|
132
|
+
class CatalogGateVerdict(_CanonicalContract):
|
|
133
|
+
"""One entry's decision, with the citations that make it checkable later.
|
|
134
|
+
|
|
135
|
+
Three digests, because a decision has three inputs and citing two of them makes the record
|
|
136
|
+
ambiguous. ``entry_digest`` and ``rights_digest`` cite what was decided about;
|
|
137
|
+
``requirement_digest`` cites what it was decided against, which supplies the other half of
|
|
138
|
+
every check here -- the lifecycle, the intended use, the required formats, the cadence ceiling
|
|
139
|
+
and the open-access and liveness switches. Without it the same entry yields ``admitted`` under
|
|
140
|
+
one requirement and ``refused`` under another and the two stored verdicts are indistinguishable
|
|
141
|
+
in their citations. ``registry.acquire``'s ``REQUEST_BINDING`` check is the same idea one layer
|
|
142
|
+
down.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
entry_coordinate: str
|
|
146
|
+
disposition: str
|
|
147
|
+
reason: str
|
|
148
|
+
checks: tuple[CatalogCheck, ...]
|
|
149
|
+
entry_digest: str
|
|
150
|
+
rights_digest: str
|
|
151
|
+
requirement_digest: str
|
|
152
|
+
schema_version: str = CATALOG_VERDICT_CONTRACT_VERSION
|
|
153
|
+
|
|
154
|
+
def __post_init__(self) -> None:
|
|
155
|
+
_constant(
|
|
156
|
+
self.schema_version,
|
|
157
|
+
CATALOG_VERDICT_CONTRACT_VERSION,
|
|
158
|
+
"verdict.schema_version",
|
|
159
|
+
)
|
|
160
|
+
_text(self.entry_coordinate, "verdict.entry_coordinate", maximum=200)
|
|
161
|
+
_choice(self.disposition, frozenset(CATALOG_DISPOSITIONS), "verdict.disposition")
|
|
162
|
+
_text(self.reason, "verdict.reason", maximum=MAX_DETAIL)
|
|
163
|
+
if not isinstance(self.checks, tuple):
|
|
164
|
+
raise SourceContractError("TYPE", "verdict.checks", "must be an immutable tuple")
|
|
165
|
+
if tuple(item.check for item in self.checks) != CATALOG_CHECKS:
|
|
166
|
+
raise SourceContractError(
|
|
167
|
+
"CATALOG_CHECK_SET",
|
|
168
|
+
"verdict.checks",
|
|
169
|
+
f"must contain each of {list(CATALOG_CHECKS)} exactly once, in order",
|
|
170
|
+
)
|
|
171
|
+
_digest(self.entry_digest, "verdict.entry_digest")
|
|
172
|
+
_digest(self.rights_digest, "verdict.rights_digest")
|
|
173
|
+
_digest(self.requirement_digest, "verdict.requirement_digest")
|
|
174
|
+
|
|
175
|
+
def to_dict(self) -> dict[str, Any]:
|
|
176
|
+
return {
|
|
177
|
+
"schema_version": self.schema_version,
|
|
178
|
+
"entry_coordinate": self.entry_coordinate,
|
|
179
|
+
"disposition": self.disposition,
|
|
180
|
+
"reason": self.reason,
|
|
181
|
+
"checks": [item.to_dict() for item in self.checks],
|
|
182
|
+
"entry_digest": self.entry_digest,
|
|
183
|
+
"rights_digest": self.rights_digest,
|
|
184
|
+
"requirement_digest": self.requirement_digest,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def gate_entries(
|
|
189
|
+
entries: tuple[CatalogEntry, ...],
|
|
190
|
+
*,
|
|
191
|
+
requirement: CatalogRequirement,
|
|
192
|
+
) -> tuple[CatalogGateVerdict, ...]:
|
|
193
|
+
"""Decide each entry against the requirement. A pure function of facts.
|
|
194
|
+
|
|
195
|
+
Note what this signature cannot express: there is no argument through which a degree of
|
|
196
|
+
textual match could arrive, and neither ``CatalogEntry`` nor ``CatalogRequirement`` has a
|
|
197
|
+
field able to carry one.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
if not isinstance(entries, tuple):
|
|
201
|
+
raise SourceContractError("TYPE", "entries", "must be an immutable tuple")
|
|
202
|
+
if not isinstance(requirement, CatalogRequirement):
|
|
203
|
+
raise SourceContractError("TYPE", "requirement", "must be a CatalogRequirement")
|
|
204
|
+
verdicts: list[CatalogGateVerdict] = []
|
|
205
|
+
for position, entry in enumerate(entries):
|
|
206
|
+
if not isinstance(entry, CatalogEntry):
|
|
207
|
+
raise SourceContractError(
|
|
208
|
+
"TYPE",
|
|
209
|
+
f"entries[{position}]",
|
|
210
|
+
"must be a CatalogEntry",
|
|
211
|
+
)
|
|
212
|
+
verdicts.append(_gate_one(entry, requirement))
|
|
213
|
+
return tuple(verdicts)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _gate_one(entry: CatalogEntry, requirement: CatalogRequirement) -> CatalogGateVerdict:
|
|
217
|
+
disposition_of_rights = rights_disposition(entry.rights.status)
|
|
218
|
+
outcomes: dict[str, tuple[bool, str]] = {}
|
|
219
|
+
|
|
220
|
+
outcomes["rights"] = (
|
|
221
|
+
disposition_of_rights == "admit",
|
|
222
|
+
f"rights status {entry.rights.status!r} is {disposition_of_rights}",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if disposition_of_rights == "admit":
|
|
226
|
+
try:
|
|
227
|
+
applicable = evaluate_obligations(
|
|
228
|
+
rights=entry.rights,
|
|
229
|
+
lifecycle=requirement.lifecycle,
|
|
230
|
+
intended_use=requirement.intended_use,
|
|
231
|
+
attached_artifact_digests=requirement.attached_license_artifact_digests,
|
|
232
|
+
)
|
|
233
|
+
except SourceContractError as error:
|
|
234
|
+
outcomes["obligations"] = (
|
|
235
|
+
False,
|
|
236
|
+
f"licence obligations are not satisfied: {error.detail}"[:MAX_DETAIL],
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
outcomes["obligations"] = (
|
|
240
|
+
True,
|
|
241
|
+
f"{len(applicable)} licence obligation(s) apply and are satisfied",
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
outcomes["obligations"] = (
|
|
245
|
+
True,
|
|
246
|
+
"not evaluated: the rights check already decided this entry",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
needs_login = requirement.require_open_access and entry.authentication_required
|
|
250
|
+
outcomes["authentication"] = (
|
|
251
|
+
not needs_login,
|
|
252
|
+
"the source requires authentication and open access was required"
|
|
253
|
+
if needs_login
|
|
254
|
+
else "no authentication is required",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
shared_formats = sorted(set(entry.data_formats) & set(requirement.required_data_formats))
|
|
258
|
+
outcomes["data_format"] = (
|
|
259
|
+
bool(shared_formats),
|
|
260
|
+
f"published formats {sorted(entry.data_formats)} "
|
|
261
|
+
f"{'include' if shared_formats else 'do not include'} "
|
|
262
|
+
f"a required format from {sorted(requirement.required_data_formats)}",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
observed_frequency = entry.observation.update_frequency_seconds
|
|
266
|
+
outcomes["cadence"] = (
|
|
267
|
+
observed_frequency <= requirement.max_update_frequency_seconds,
|
|
268
|
+
f"the source updates every {observed_frequency}s against a required "
|
|
269
|
+
f"{requirement.max_update_frequency_seconds}s",
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
live_status = entry.observation.live_status
|
|
273
|
+
outcomes["liveness"] = (
|
|
274
|
+
not (requirement.require_live and live_status != "live"),
|
|
275
|
+
f"the last probe found the source {live_status!r}",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
checks = tuple(
|
|
279
|
+
CatalogCheck(
|
|
280
|
+
check=name,
|
|
281
|
+
outcome="pass" if outcomes[name][0] else "fail",
|
|
282
|
+
detail=outcomes[name][1],
|
|
283
|
+
)
|
|
284
|
+
for name in CATALOG_CHECKS
|
|
285
|
+
)
|
|
286
|
+
failed = [item.check for item in checks if item.outcome != "pass"]
|
|
287
|
+
|
|
288
|
+
# Precedence mirrors the fitness adjudicator exactly: escalation first, then any failure.
|
|
289
|
+
if disposition_of_rights == "escalate":
|
|
290
|
+
disposition = "human_escalation_required"
|
|
291
|
+
reason = "rights are unclear and require human legal/product escalation"
|
|
292
|
+
elif failed:
|
|
293
|
+
disposition = "refused"
|
|
294
|
+
reason = f"failed checks: {', '.join(failed)}"
|
|
295
|
+
else:
|
|
296
|
+
disposition = "admitted"
|
|
297
|
+
reason = "every required fact check passes"
|
|
298
|
+
|
|
299
|
+
return CatalogGateVerdict(
|
|
300
|
+
entry_coordinate=entry.coordinate,
|
|
301
|
+
disposition=disposition,
|
|
302
|
+
reason=reason,
|
|
303
|
+
checks=checks,
|
|
304
|
+
entry_digest=entry.digest,
|
|
305
|
+
rights_digest=entry.rights.digest,
|
|
306
|
+
requirement_digest=requirement.digest,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _text(value: Any, path: str, *, maximum: int) -> str:
|
|
311
|
+
if not isinstance(value, str) or not value or len(value) > maximum:
|
|
312
|
+
raise SourceContractError(
|
|
313
|
+
"TEXT",
|
|
314
|
+
path,
|
|
315
|
+
f"must be a non-empty string no longer than {maximum} characters",
|
|
316
|
+
)
|
|
317
|
+
return value
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _constant(value: Any, expected: str, path: str) -> None:
|
|
321
|
+
if value != expected:
|
|
322
|
+
raise SourceContractError("VERSION", path, f"must equal {expected!r}")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _choice(value: Any, allowed: frozenset[str] | set[str], path: str) -> str:
|
|
326
|
+
text = _text(value, path, maximum=128)
|
|
327
|
+
if text not in allowed:
|
|
328
|
+
raise SourceContractError("ENUM", path, f"must be one of {sorted(allowed)}")
|
|
329
|
+
return text
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _choices(values: Any, allowed: frozenset[str], path: str, *, maximum: int) -> None:
|
|
333
|
+
if not isinstance(values, tuple):
|
|
334
|
+
raise SourceContractError("TYPE", path, "must be an immutable tuple")
|
|
335
|
+
if not values:
|
|
336
|
+
raise SourceContractError("EMPTY", path, "must not be empty")
|
|
337
|
+
if len(values) > maximum:
|
|
338
|
+
raise SourceContractError("LIMIT", path, f"must contain at most {maximum} values")
|
|
339
|
+
for position, value in enumerate(values):
|
|
340
|
+
_choice(value, allowed, f"{path}[{position}]")
|
|
341
|
+
if len(set(values)) != len(values):
|
|
342
|
+
raise SourceContractError("DUPLICATE", path, "values must be unique")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _digest(value: Any, path: str) -> str:
|
|
346
|
+
text = _text(value, path, maximum=64)
|
|
347
|
+
if len(text) != 64 or any(char not in "0123456789abcdef" for char in text):
|
|
348
|
+
raise SourceContractError("SHA256", path, "must be a lowercase 64-character SHA-256")
|
|
349
|
+
return text
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _digests(values: Any, path: str, *, maximum: int) -> None:
|
|
353
|
+
if not isinstance(values, tuple):
|
|
354
|
+
raise SourceContractError("TYPE", path, "must be an immutable tuple")
|
|
355
|
+
if len(values) > maximum:
|
|
356
|
+
raise SourceContractError("LIMIT", path, f"must contain at most {maximum} values")
|
|
357
|
+
for position, value in enumerate(values):
|
|
358
|
+
_digest(value, f"{path}[{position}]")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _integer(value: Any, path: str, *, minimum: int, maximum: int) -> int:
|
|
362
|
+
if type(value) is not int or not minimum <= value <= maximum:
|
|
363
|
+
raise SourceContractError(
|
|
364
|
+
"INTEGER",
|
|
365
|
+
path,
|
|
366
|
+
f"must be an integer in [{minimum}, {maximum}]",
|
|
367
|
+
)
|
|
368
|
+
return value
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _boolean(value: Any, path: str) -> bool:
|
|
372
|
+
if type(value) is not bool:
|
|
373
|
+
raise SourceContractError("BOOLEAN", path, "must be a boolean")
|
|
374
|
+
return value
|