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,254 @@
|
|
|
1
|
+
"""Human rights decisions that move flagged provider records out of the queue.
|
|
2
|
+
|
|
3
|
+
A provider that states no recognized licence puts every one of its records into the rights
|
|
4
|
+
queue, and the queue has no exit inside the machine, because rights are a human question the
|
|
5
|
+
harvest cannot answer. This module is the exit. One decision document carries, per record, a
|
|
6
|
+
complete rights claim a person authored, the digest of the exact published facts that person
|
|
7
|
+
looked at, a reference to where the decision was made, and when it was made. The delta
|
|
8
|
+
classifier then admits an authored candidate for a cleared record only when the candidate's
|
|
9
|
+
rights are exactly the decided rights, and refuses the moment the provider's published facts
|
|
10
|
+
drift from what the person saw -- a decision is about one spelling of a record's facts, never
|
|
11
|
+
about a record id forever. The binding is the record's ``facts_sha256``, deliberately not its
|
|
12
|
+
full observation digest: a byte-identical record re-observed at a new time, or sharing a page
|
|
13
|
+
with an unrelated changed record, does not void a standing decision, because the person
|
|
14
|
+
decided about the facts and the facts have not moved.
|
|
15
|
+
|
|
16
|
+
Two decisions exist and both are decisions. ``cleared`` says a person established a lawful
|
|
17
|
+
basis; the record leaves the queue and demands a candidate. ``prohibited`` says a person
|
|
18
|
+
established the opposite; the record stays out of admission with the decision on it, so the
|
|
19
|
+
queue distinguishes "nobody has looked" from "somebody looked and said no". A decision whose
|
|
20
|
+
rights say ``unclear`` is refused outright: deciding nothing is not a decision, and the queue
|
|
21
|
+
already records that state without a document.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import hashlib
|
|
27
|
+
import os
|
|
28
|
+
import stat
|
|
29
|
+
from collections.abc import Mapping
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from mostlyright.data_harness.canonical import parse_canonical_json
|
|
35
|
+
from mostlyright.data_harness.sources.catalog.contracts import rights_evidence_from_dict
|
|
36
|
+
from mostlyright.data_harness.sources.contracts import (
|
|
37
|
+
RightsEvidence,
|
|
38
|
+
SourceContractError,
|
|
39
|
+
require_canonical_timestamp,
|
|
40
|
+
)
|
|
41
|
+
from mostlyright.data_harness.sources.rights_rule import rights_disposition
|
|
42
|
+
|
|
43
|
+
RIGHTS_DECISIONS_SCHEMA = "mr-data-catalog-rights-decisions.v1"
|
|
44
|
+
|
|
45
|
+
#: Decision documents are per-record prose and evidence references; four MiB holds thousands
|
|
46
|
+
#: of reviewed decisions, and a document that cannot fit here is not a reviewed set of them.
|
|
47
|
+
MAX_RIGHTS_DECISIONS_BYTES = 4 * 1024 * 1024
|
|
48
|
+
|
|
49
|
+
_DIGEST_LENGTH = 64
|
|
50
|
+
_MAX_RECORD_ID = 200
|
|
51
|
+
_MAX_REFERENCE = 200
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _is_digest(value: Any) -> bool:
|
|
55
|
+
return (
|
|
56
|
+
isinstance(value, str)
|
|
57
|
+
and len(value) == _DIGEST_LENGTH
|
|
58
|
+
and all(character in "0123456789abcdef" for character in value)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _bounded_text(value: Any, path: str, maximum: int) -> str:
|
|
63
|
+
if not isinstance(value, str) or not value.strip() or len(value) > maximum:
|
|
64
|
+
raise SourceContractError(
|
|
65
|
+
"RIGHTS_DECISION_FIELD",
|
|
66
|
+
path,
|
|
67
|
+
f"must be nonempty text of at most {maximum} characters",
|
|
68
|
+
)
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class RightsDecision:
|
|
74
|
+
"""One human rights decision, bound to the exact provider record it was made against."""
|
|
75
|
+
|
|
76
|
+
record_id: str
|
|
77
|
+
record_facts_sha256: str
|
|
78
|
+
human_decision_reference: str
|
|
79
|
+
decided_at: str
|
|
80
|
+
rights: RightsEvidence
|
|
81
|
+
|
|
82
|
+
def __post_init__(self) -> None:
|
|
83
|
+
_bounded_text(self.record_id, "rights_decision.record_id", _MAX_RECORD_ID)
|
|
84
|
+
if not _is_digest(self.record_facts_sha256):
|
|
85
|
+
raise SourceContractError(
|
|
86
|
+
"RIGHTS_DECISION_FIELD",
|
|
87
|
+
"rights_decision.record_facts_sha256",
|
|
88
|
+
"must be the lowercase SHA-256 of the decided record's published facts",
|
|
89
|
+
)
|
|
90
|
+
_bounded_text(
|
|
91
|
+
self.human_decision_reference,
|
|
92
|
+
"rights_decision.human_decision_reference",
|
|
93
|
+
_MAX_REFERENCE,
|
|
94
|
+
)
|
|
95
|
+
require_canonical_timestamp(self.decided_at, "rights_decision.decided_at")
|
|
96
|
+
if not isinstance(self.rights, RightsEvidence):
|
|
97
|
+
raise SourceContractError(
|
|
98
|
+
"RIGHTS_DECISION_FIELD",
|
|
99
|
+
"rights_decision.rights",
|
|
100
|
+
"must be a complete RightsEvidence",
|
|
101
|
+
)
|
|
102
|
+
disposition = rights_disposition(self.rights.status)
|
|
103
|
+
if disposition == "escalate":
|
|
104
|
+
raise SourceContractError(
|
|
105
|
+
"RIGHTS_DECISION_UNDECIDED",
|
|
106
|
+
"rights_decision.rights.status",
|
|
107
|
+
"a decision that says unclear decides nothing; the queue already records "
|
|
108
|
+
"that state without a document",
|
|
109
|
+
)
|
|
110
|
+
if disposition == "admit":
|
|
111
|
+
# An admitting claim carries the approval authority inside the rights, exactly as
|
|
112
|
+
# the entry it will become must carry it, and the two spellings must be one.
|
|
113
|
+
if self.rights.human_decision_reference != self.human_decision_reference:
|
|
114
|
+
raise SourceContractError(
|
|
115
|
+
"RIGHTS_DECISION_BINDING",
|
|
116
|
+
"rights_decision.rights.human_decision_reference",
|
|
117
|
+
"the decided rights must carry exactly this decision's reference",
|
|
118
|
+
)
|
|
119
|
+
# A prohibiting claim cannot embed approval authority at all: the rights contract
|
|
120
|
+
# refuses that spelling at construction (RIGHTS_AUTHORITY_CONFLICT), so the
|
|
121
|
+
# prohibiting decision's reference lives on the decision alone by construction.
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def cleared(self) -> bool:
|
|
125
|
+
"""True when this decision admits the record; false when it prohibits it."""
|
|
126
|
+
|
|
127
|
+
return rights_disposition(self.rights.status) == "admit"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _read_bounded_regular(path: Path) -> bytes:
|
|
131
|
+
flags = os.O_RDONLY | os.O_NONBLOCK | getattr(os, "O_NOFOLLOW", 0)
|
|
132
|
+
try:
|
|
133
|
+
descriptor = os.open(path, flags)
|
|
134
|
+
except OSError as error:
|
|
135
|
+
raise SourceContractError(
|
|
136
|
+
"RIGHTS_DECISIONS_UNAVAILABLE",
|
|
137
|
+
"rights_decisions",
|
|
138
|
+
"the decision document could not be opened as a regular file",
|
|
139
|
+
) from error
|
|
140
|
+
try:
|
|
141
|
+
metadata = os.fstat(descriptor)
|
|
142
|
+
if (
|
|
143
|
+
not stat.S_ISREG(metadata.st_mode)
|
|
144
|
+
or metadata.st_nlink != 1
|
|
145
|
+
or metadata.st_size > MAX_RIGHTS_DECISIONS_BYTES
|
|
146
|
+
):
|
|
147
|
+
raise SourceContractError(
|
|
148
|
+
"RIGHTS_DECISIONS_UNAVAILABLE",
|
|
149
|
+
"rights_decisions",
|
|
150
|
+
"the decision document must be one bounded single-link regular file",
|
|
151
|
+
)
|
|
152
|
+
chunks: list[bytes] = []
|
|
153
|
+
while True:
|
|
154
|
+
try:
|
|
155
|
+
chunk = os.read(descriptor, 1 << 16)
|
|
156
|
+
except OSError as error:
|
|
157
|
+
raise SourceContractError(
|
|
158
|
+
"RIGHTS_DECISIONS_UNAVAILABLE",
|
|
159
|
+
"rights_decisions",
|
|
160
|
+
"the decision document could not be read",
|
|
161
|
+
) from error
|
|
162
|
+
if not chunk:
|
|
163
|
+
break
|
|
164
|
+
chunks.append(chunk)
|
|
165
|
+
if sum(len(item) for item in chunks) > MAX_RIGHTS_DECISIONS_BYTES:
|
|
166
|
+
raise SourceContractError(
|
|
167
|
+
"RIGHTS_DECISIONS_UNAVAILABLE",
|
|
168
|
+
"rights_decisions",
|
|
169
|
+
"the decision document must be one bounded single-link regular file",
|
|
170
|
+
)
|
|
171
|
+
return b"".join(chunks)
|
|
172
|
+
except OSError as error:
|
|
173
|
+
raise SourceContractError(
|
|
174
|
+
"RIGHTS_DECISIONS_UNAVAILABLE",
|
|
175
|
+
"rights_decisions",
|
|
176
|
+
"the decision document could not be read",
|
|
177
|
+
) from error
|
|
178
|
+
finally:
|
|
179
|
+
os.close(descriptor)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def load_rights_decisions(path: Path, expected_sha256: str) -> Mapping[str, RightsDecision]:
|
|
183
|
+
"""Load one digest-bound decision document into typed decisions by record id."""
|
|
184
|
+
|
|
185
|
+
if not _is_digest(expected_sha256):
|
|
186
|
+
raise SourceContractError(
|
|
187
|
+
"RIGHTS_DECISIONS_DIGEST",
|
|
188
|
+
"rights_decisions_sha256",
|
|
189
|
+
"must be a lowercase SHA-256",
|
|
190
|
+
)
|
|
191
|
+
raw = _read_bounded_regular(path)
|
|
192
|
+
if hashlib.sha256(raw).hexdigest() != expected_sha256:
|
|
193
|
+
raise SourceContractError(
|
|
194
|
+
"RIGHTS_DECISIONS_DIGEST",
|
|
195
|
+
"rights_decisions",
|
|
196
|
+
"decision bytes differ from the required digest",
|
|
197
|
+
)
|
|
198
|
+
value = parse_canonical_json(raw)
|
|
199
|
+
expected_fields = {"schema_version", "decisions_by_record_id"}
|
|
200
|
+
if (
|
|
201
|
+
not isinstance(value, dict)
|
|
202
|
+
or set(value) != expected_fields
|
|
203
|
+
or value["schema_version"] != RIGHTS_DECISIONS_SCHEMA
|
|
204
|
+
):
|
|
205
|
+
raise SourceContractError(
|
|
206
|
+
"RIGHTS_DECISIONS_SCHEMA",
|
|
207
|
+
"rights_decisions",
|
|
208
|
+
"decision fields or version differ",
|
|
209
|
+
)
|
|
210
|
+
raw_decisions = value["decisions_by_record_id"]
|
|
211
|
+
if not isinstance(raw_decisions, dict):
|
|
212
|
+
raise SourceContractError(
|
|
213
|
+
"RIGHTS_DECISIONS_SCHEMA",
|
|
214
|
+
"rights_decisions.decisions_by_record_id",
|
|
215
|
+
"must be a mapping of record ids to decisions",
|
|
216
|
+
)
|
|
217
|
+
decisions: dict[str, RightsDecision] = {}
|
|
218
|
+
for key, payload in raw_decisions.items():
|
|
219
|
+
if not isinstance(key, str) or not isinstance(payload, dict):
|
|
220
|
+
raise SourceContractError(
|
|
221
|
+
"RIGHTS_DECISIONS_SCHEMA",
|
|
222
|
+
"rights_decisions.decisions_by_record_id",
|
|
223
|
+
"keys must be record ids and values must be decision objects",
|
|
224
|
+
)
|
|
225
|
+
expected_decision_fields = {
|
|
226
|
+
"record_id",
|
|
227
|
+
"record_facts_sha256",
|
|
228
|
+
"human_decision_reference",
|
|
229
|
+
"decided_at",
|
|
230
|
+
"rights",
|
|
231
|
+
}
|
|
232
|
+
if set(payload) != expected_decision_fields:
|
|
233
|
+
raise SourceContractError(
|
|
234
|
+
"RIGHTS_DECISIONS_SCHEMA",
|
|
235
|
+
f"rights_decisions.decisions_by_record_id.{key}",
|
|
236
|
+
"decision fields differ",
|
|
237
|
+
)
|
|
238
|
+
if payload["record_id"] != key:
|
|
239
|
+
raise SourceContractError(
|
|
240
|
+
"RIGHTS_DECISIONS_SCHEMA",
|
|
241
|
+
f"rights_decisions.decisions_by_record_id.{key}",
|
|
242
|
+
"the mapping key must equal the decision's record id",
|
|
243
|
+
)
|
|
244
|
+
decisions[key] = RightsDecision(
|
|
245
|
+
record_id=payload["record_id"],
|
|
246
|
+
record_facts_sha256=payload["record_facts_sha256"],
|
|
247
|
+
human_decision_reference=payload["human_decision_reference"],
|
|
248
|
+
decided_at=payload["decided_at"],
|
|
249
|
+
rights=rights_evidence_from_dict(
|
|
250
|
+
payload["rights"],
|
|
251
|
+
path=f"rights_decisions.decisions_by_record_id.{key}.rights",
|
|
252
|
+
),
|
|
253
|
+
)
|
|
254
|
+
return decisions
|