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,62 @@
|
|
|
1
|
+
"""The record one hosted Table handoff leaves behind, and the one way to read it.
|
|
2
|
+
|
|
3
|
+
The record answers a question a folder full of verified bytes cannot: which released version, which
|
|
4
|
+
hosted run, and which fingerprints these local bytes came from. Nothing is admitted anywhere on the
|
|
5
|
+
strength of it -- the viewer verifies the Build and re-derives the notebook for itself, exactly as
|
|
6
|
+
it does for a Build made here -- so this carries no authority and is never consulted by a decision.
|
|
7
|
+
It exists so that what is already admitted can be attributed.
|
|
8
|
+
|
|
9
|
+
It lives in its own module because two very different callers need it: the lane that writes it,
|
|
10
|
+
which reaches Studio, and the viewer, which reaches nothing. A viewer that had to import the
|
|
11
|
+
downloading lane to read one small local file would be importing a Studio client to render a
|
|
12
|
+
notebook.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import stat
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from mostlyright.data_harness import canonical
|
|
23
|
+
|
|
24
|
+
HANDOFF_RECEIPT_SCHEMA = "mostlyright-hosted-table-handoff-receipt.v2"
|
|
25
|
+
HANDOFF_RELATIVE_PATH = Path("evidence") / "hosted-table" / "handoff.json"
|
|
26
|
+
MAX_HANDOFF_RECEIPT_BYTES = 64 * 1024
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def read_handoff_receipt(
|
|
30
|
+
run_dir: Path, *, max_bytes: int = MAX_HANDOFF_RECEIPT_BYTES
|
|
31
|
+
) -> dict[str, Any] | None:
|
|
32
|
+
"""Return the handoff record under ``run_dir``, or nothing, without following a link."""
|
|
33
|
+
|
|
34
|
+
path = Path(run_dir) / HANDOFF_RELATIVE_PATH
|
|
35
|
+
try:
|
|
36
|
+
descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
|
|
37
|
+
except OSError:
|
|
38
|
+
return None
|
|
39
|
+
try:
|
|
40
|
+
info = os.fstat(descriptor)
|
|
41
|
+
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1 or info.st_size > max_bytes:
|
|
42
|
+
return None
|
|
43
|
+
raw = os.read(descriptor, max_bytes)
|
|
44
|
+
except OSError:
|
|
45
|
+
return None
|
|
46
|
+
finally:
|
|
47
|
+
os.close(descriptor)
|
|
48
|
+
try:
|
|
49
|
+
value = canonical.parse_canonical_json(raw)
|
|
50
|
+
except canonical.CanonicalJSONError:
|
|
51
|
+
return None
|
|
52
|
+
if not isinstance(value, dict) or value.get("schema_version") != HANDOFF_RECEIPT_SCHEMA:
|
|
53
|
+
return None
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"HANDOFF_RECEIPT_SCHEMA",
|
|
59
|
+
"HANDOFF_RELATIVE_PATH",
|
|
60
|
+
"MAX_HANDOFF_RECEIPT_BYTES",
|
|
61
|
+
"read_handoff_receipt",
|
|
62
|
+
]
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""Frozen, capability-free wire contracts for one hosted ingestion delivery.
|
|
2
|
+
|
|
3
|
+
This module is intentionally independent from the generated Studio client. It is the narrow
|
|
4
|
+
contract a Cloud Run Job receives *after* redeeming its opaque dispatch id. The document carries
|
|
5
|
+
only identities, immutable source pins, GCS object coordinates and a deadline; it never carries a
|
|
6
|
+
Studio bearer credential, signed URL, bucket authority, or source credential. Studio owns the
|
|
7
|
+
matching schema and translates its typed ``sha256:...`` values at its one adapter boundary; the
|
|
8
|
+
Harness contracts deliberately use the existing bare 64-hex ingestion digests.
|
|
9
|
+
|
|
10
|
+
The persisted delivery envelope binds a mutable, delivery-scoped manifest to the exact execution
|
|
11
|
+
and attempt which may advance it. Chunks deliberately remain content-addressed under a shared,
|
|
12
|
+
reviewed prefix, but two deliveries can never CAS the same manifest object.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from datetime import UTC, datetime
|
|
20
|
+
from typing import Any, Self
|
|
21
|
+
from uuid import UUID
|
|
22
|
+
|
|
23
|
+
from mostlyright.data_harness.canonical import canonical_sha256
|
|
24
|
+
from mostlyright.data_harness.ingestion.contracts import (
|
|
25
|
+
IngestionCorrupt,
|
|
26
|
+
IngestionError,
|
|
27
|
+
IngestionManifestV1,
|
|
28
|
+
SourceVersionPinV1,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
INGESTION_MANIFEST_REF_SCHEMA = "mostlyright-ingestion-manifest-ref.v1"
|
|
32
|
+
INGESTION_DELIVERY_BINDING_SCHEMA = "mostlyright-ingestion-delivery-binding.v1"
|
|
33
|
+
INGESTION_DELIVERY_MANIFEST_SCHEMA = "mostlyright-ingestion-delivery-manifest.v1"
|
|
34
|
+
HOSTED_INGESTION_DISPATCH_SCHEMA = "mostlyright-ingestion-dispatch.v1"
|
|
35
|
+
PUBLIC_OFFSET_CONTINUATION_SCHEMA = "mostlyright-ingestion-public-offset.v1"
|
|
36
|
+
|
|
37
|
+
MAX_MANIFEST_REF_BYTES = 16 * 1024 * 1024
|
|
38
|
+
MAX_PREFIX_LENGTH = 512
|
|
39
|
+
MAX_OBJECT_NAME_LENGTH = 1024
|
|
40
|
+
MAX_SEGMENT_ID_LENGTH = 128
|
|
41
|
+
MAX_DELIVERY_DEADLINE_SECONDS = 15 * 60
|
|
42
|
+
|
|
43
|
+
_DIGEST = re.compile(r"[0-9a-f]{64}\Z")
|
|
44
|
+
_GENERATION = re.compile(r"[1-9][0-9]*\Z")
|
|
45
|
+
_PREFIX_SEGMENT = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z")
|
|
46
|
+
_SEGMENT_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z")
|
|
47
|
+
_RFC3339 = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]{1,6})?Z\Z")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _error(code: str, path: str, detail: str) -> IngestionError:
|
|
51
|
+
return IngestionError(code, path, detail)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _require_exact(value: object, keys: frozenset[str], path: str) -> dict[str, Any]:
|
|
55
|
+
if not isinstance(value, dict) or set(value) != keys:
|
|
56
|
+
raise _error("INGESTION_DELIVERY_DOCUMENT", path, "has unsupported or missing members")
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _require_digest(value: object, path: str) -> str:
|
|
61
|
+
if not isinstance(value, str) or _DIGEST.fullmatch(value) is None:
|
|
62
|
+
raise _error("INGESTION_DIGEST", path, "must be one lowercase SHA-256 digest")
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _require_uuid4(value: object, path: str) -> str:
|
|
67
|
+
if not isinstance(value, str):
|
|
68
|
+
raise _error("INGESTION_DELIVERY_ID", path, "must be one canonical UUID4")
|
|
69
|
+
try:
|
|
70
|
+
parsed = UUID(value)
|
|
71
|
+
except ValueError as error:
|
|
72
|
+
raise _error("INGESTION_DELIVERY_ID", path, "must be one canonical UUID4") from error
|
|
73
|
+
if parsed.version != 4 or str(parsed) != value:
|
|
74
|
+
raise _error("INGESTION_DELIVERY_ID", path, "must be one canonical UUID4")
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _require_positive_int(value: object, path: str, *, maximum: int = (1 << 63) - 1) -> int:
|
|
79
|
+
if isinstance(value, bool) or not isinstance(value, int) or not 0 < value <= maximum:
|
|
80
|
+
raise _error("INGESTION_DELIVERY_NUMBER", path, "must be one bounded positive integer")
|
|
81
|
+
return value
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _require_nonnegative_int(value: object, path: str, *, maximum: int = (1 << 63) - 1) -> int:
|
|
85
|
+
if isinstance(value, bool) or not isinstance(value, int) or not 0 <= value <= maximum:
|
|
86
|
+
raise _error("INGESTION_DELIVERY_NUMBER", path, "must be one bounded non-negative integer")
|
|
87
|
+
return value
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _require_prefix(value: object, path: str) -> str:
|
|
91
|
+
if not isinstance(value, str) or not value or len(value) > MAX_PREFIX_LENGTH:
|
|
92
|
+
raise _error("INGESTION_DELIVERY_PREFIX", path, "must be one bounded object prefix")
|
|
93
|
+
if any(_PREFIX_SEGMENT.fullmatch(part) is None for part in value.split("/")):
|
|
94
|
+
raise _error("INGESTION_DELIVERY_PREFIX", path, "contains an unsafe object-prefix segment")
|
|
95
|
+
return value
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _require_object_name(value: object, path: str) -> str:
|
|
99
|
+
"""Validate a GCS object name without imposing the shorter prefix limit on its suffix."""
|
|
100
|
+
|
|
101
|
+
if not isinstance(value, str) or not value or len(value) > MAX_OBJECT_NAME_LENGTH:
|
|
102
|
+
raise _error("INGESTION_DELIVERY_OBJECT", path, "must be one bounded object name")
|
|
103
|
+
if any(_PREFIX_SEGMENT.fullmatch(part) is None for part in value.split("/")):
|
|
104
|
+
raise _error("INGESTION_DELIVERY_OBJECT", path, "contains an unsafe object-name segment")
|
|
105
|
+
return value
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _require_delivery_prefix(value: object, path: str, delivery_id: str) -> str:
|
|
109
|
+
"""A mutable manifest prefix must name precisely the delivery that may advance it."""
|
|
110
|
+
|
|
111
|
+
prefix = _require_prefix(value, path)
|
|
112
|
+
if prefix.rsplit("/", 1)[-1] != delivery_id:
|
|
113
|
+
raise _error(
|
|
114
|
+
"INGESTION_DELIVERY_PREFIX",
|
|
115
|
+
path,
|
|
116
|
+
"must end in this delivery's canonical identifier",
|
|
117
|
+
)
|
|
118
|
+
return prefix
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _require_run_id(value: object, path: str) -> str:
|
|
122
|
+
if not isinstance(value, str) or _SEGMENT_ID.fullmatch(value) is None:
|
|
123
|
+
raise _error("INGESTION_RUN_ID", path, "must be one safe bounded run id")
|
|
124
|
+
return value
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _require_segment_id(value: object, path: str) -> str:
|
|
128
|
+
if not isinstance(value, str) or _SEGMENT_ID.fullmatch(value) is None:
|
|
129
|
+
raise _error("INGESTION_SEGMENT_ID", path, "must be one safe bounded segment id")
|
|
130
|
+
return value
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _require_deadline(value: object, path: str) -> str:
|
|
134
|
+
if not isinstance(value, str) or _RFC3339.fullmatch(value) is None:
|
|
135
|
+
raise _error("INGESTION_DELIVERY_DEADLINE", path, "must be one UTC RFC3339 timestamp")
|
|
136
|
+
try:
|
|
137
|
+
datetime.fromisoformat(value.removesuffix("Z") + "+00:00")
|
|
138
|
+
except ValueError as error:
|
|
139
|
+
raise _error("INGESTION_DELIVERY_DEADLINE", path, "is not a real UTC timestamp") from error
|
|
140
|
+
return value
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def parse_delivery_deadline(value: str) -> datetime:
|
|
144
|
+
"""Return a UTC deadline after the frozen wire syntax has been validated."""
|
|
145
|
+
|
|
146
|
+
_require_deadline(value, "delivery.deadline")
|
|
147
|
+
return datetime.fromisoformat(value.removesuffix("Z") + "+00:00").astimezone(UTC)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class IngestionManifestReferenceV1:
|
|
152
|
+
"""An exact GCS generation and raw-document digest, never a bucket authority."""
|
|
153
|
+
|
|
154
|
+
object_name: str
|
|
155
|
+
generation: str
|
|
156
|
+
manifest_sha256: str
|
|
157
|
+
byte_length: int
|
|
158
|
+
schema_version: str = INGESTION_MANIFEST_REF_SCHEMA
|
|
159
|
+
|
|
160
|
+
def __post_init__(self) -> None:
|
|
161
|
+
if self.schema_version != INGESTION_MANIFEST_REF_SCHEMA:
|
|
162
|
+
raise _error(
|
|
163
|
+
"INGESTION_MANIFEST_REF_SCHEMA", "manifest_ref.schema_version", "is unsupported"
|
|
164
|
+
)
|
|
165
|
+
_require_object_name(self.object_name, "manifest_ref.object_name")
|
|
166
|
+
if _GENERATION.fullmatch(self.generation) is None:
|
|
167
|
+
raise _error(
|
|
168
|
+
"INGESTION_MANIFEST_REF_GENERATION", "manifest_ref.generation", "must be positive"
|
|
169
|
+
)
|
|
170
|
+
_require_digest(self.manifest_sha256, "manifest_ref.manifest_sha256")
|
|
171
|
+
if (
|
|
172
|
+
isinstance(self.byte_length, bool)
|
|
173
|
+
or not isinstance(self.byte_length, int)
|
|
174
|
+
or not 0 < self.byte_length <= MAX_MANIFEST_REF_BYTES
|
|
175
|
+
):
|
|
176
|
+
raise _error(
|
|
177
|
+
"INGESTION_MANIFEST_REF_LENGTH",
|
|
178
|
+
"manifest_ref.byte_length",
|
|
179
|
+
"is outside the supported range",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def to_dict(self) -> dict[str, str | int]:
|
|
183
|
+
return {
|
|
184
|
+
"schema_version": self.schema_version,
|
|
185
|
+
"object_name": self.object_name,
|
|
186
|
+
"generation": self.generation,
|
|
187
|
+
"manifest_sha256": self.manifest_sha256,
|
|
188
|
+
"byte_length": self.byte_length,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def from_dict(cls, value: object) -> Self:
|
|
193
|
+
document = _require_exact(
|
|
194
|
+
value,
|
|
195
|
+
frozenset(
|
|
196
|
+
{"schema_version", "object_name", "generation", "manifest_sha256", "byte_length"}
|
|
197
|
+
),
|
|
198
|
+
"manifest_ref",
|
|
199
|
+
)
|
|
200
|
+
return cls(
|
|
201
|
+
schema_version=document["schema_version"],
|
|
202
|
+
object_name=document["object_name"],
|
|
203
|
+
generation=document["generation"],
|
|
204
|
+
manifest_sha256=document["manifest_sha256"],
|
|
205
|
+
byte_length=document["byte_length"],
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def require_for_prefix(self, *, prefix: str, run_id: str) -> None:
|
|
209
|
+
"""Reject a ref outside the exact reviewed delivery namespace and run coordinate."""
|
|
210
|
+
|
|
211
|
+
checked_prefix = _require_prefix(prefix, "manifest_prefix")
|
|
212
|
+
checked_run_id = _require_run_id(run_id, "run_id")
|
|
213
|
+
expected = f"{checked_prefix}/runs/{checked_run_id}.json"
|
|
214
|
+
if self.object_name != expected:
|
|
215
|
+
raise _error(
|
|
216
|
+
"INGESTION_MANIFEST_REF_PATH",
|
|
217
|
+
"manifest_ref.object_name",
|
|
218
|
+
"does not name this delivery's manifest object",
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True)
|
|
223
|
+
class PublicOffsetContinuationV1:
|
|
224
|
+
"""The only first-slice continuation: a deterministic offset in immutable public bytes."""
|
|
225
|
+
|
|
226
|
+
offset: int
|
|
227
|
+
schema_version: str = PUBLIC_OFFSET_CONTINUATION_SCHEMA
|
|
228
|
+
|
|
229
|
+
def __post_init__(self) -> None:
|
|
230
|
+
if self.schema_version != PUBLIC_OFFSET_CONTINUATION_SCHEMA:
|
|
231
|
+
raise _error(
|
|
232
|
+
"INGESTION_CONTINUATION_SCHEMA", "continuation.schema_version", "is unsupported"
|
|
233
|
+
)
|
|
234
|
+
_require_nonnegative_int(self.offset, "continuation.offset")
|
|
235
|
+
|
|
236
|
+
def to_dict(self) -> dict[str, str | int]:
|
|
237
|
+
return {"schema_version": self.schema_version, "offset": self.offset}
|
|
238
|
+
|
|
239
|
+
@classmethod
|
|
240
|
+
def from_dict(cls, value: object) -> Self:
|
|
241
|
+
document = _require_exact(value, frozenset({"schema_version", "offset"}), "continuation")
|
|
242
|
+
return cls(schema_version=document["schema_version"], offset=document["offset"])
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@dataclass(frozen=True)
|
|
246
|
+
class IngestionDeliveryBindingV1:
|
|
247
|
+
"""Identity/fence context sealed around every delivery-scoped manifest document."""
|
|
248
|
+
|
|
249
|
+
delivery_id: str
|
|
250
|
+
execution_id: str
|
|
251
|
+
run_id: str
|
|
252
|
+
segment_id: str
|
|
253
|
+
source_pin_sha256: str
|
|
254
|
+
attempt_id: str
|
|
255
|
+
attempt_generation: int
|
|
256
|
+
attempt_fence: int
|
|
257
|
+
checkpoint_epoch: int
|
|
258
|
+
checkpoint_sequence: int
|
|
259
|
+
parent_checkpoint_digest: str | None
|
|
260
|
+
deadline: str
|
|
261
|
+
schema_version: str = INGESTION_DELIVERY_BINDING_SCHEMA
|
|
262
|
+
|
|
263
|
+
def __post_init__(self) -> None:
|
|
264
|
+
if self.schema_version != INGESTION_DELIVERY_BINDING_SCHEMA:
|
|
265
|
+
raise _error("INGESTION_DELIVERY_SCHEMA", "delivery.schema_version", "is unsupported")
|
|
266
|
+
_require_uuid4(self.delivery_id, "delivery.delivery_id")
|
|
267
|
+
_require_uuid4(self.execution_id, "delivery.execution_id")
|
|
268
|
+
_require_run_id(self.run_id, "delivery.run_id")
|
|
269
|
+
_require_segment_id(self.segment_id, "delivery.segment_id")
|
|
270
|
+
_require_digest(self.source_pin_sha256, "delivery.source_pin_sha256")
|
|
271
|
+
_require_uuid4(self.attempt_id, "delivery.attempt_id")
|
|
272
|
+
_require_positive_int(self.attempt_generation, "delivery.attempt_generation")
|
|
273
|
+
_require_positive_int(self.attempt_fence, "delivery.attempt_fence")
|
|
274
|
+
_require_nonnegative_int(self.checkpoint_epoch, "delivery.checkpoint_epoch")
|
|
275
|
+
_require_nonnegative_int(self.checkpoint_sequence, "delivery.checkpoint_sequence")
|
|
276
|
+
if self.parent_checkpoint_digest is not None:
|
|
277
|
+
_require_digest(self.parent_checkpoint_digest, "delivery.parent_checkpoint_digest")
|
|
278
|
+
_require_deadline(self.deadline, "delivery.deadline")
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def deadline_at(self) -> datetime:
|
|
282
|
+
return parse_delivery_deadline(self.deadline)
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def digest(self) -> str:
|
|
286
|
+
return canonical_sha256(self.to_dict())
|
|
287
|
+
|
|
288
|
+
def to_dict(self) -> dict[str, str | int | None]:
|
|
289
|
+
return {
|
|
290
|
+
"schema_version": self.schema_version,
|
|
291
|
+
"delivery_id": self.delivery_id,
|
|
292
|
+
"execution_id": self.execution_id,
|
|
293
|
+
"run_id": self.run_id,
|
|
294
|
+
"segment_id": self.segment_id,
|
|
295
|
+
"source_pin_sha256": self.source_pin_sha256,
|
|
296
|
+
"attempt_id": self.attempt_id,
|
|
297
|
+
"attempt_generation": self.attempt_generation,
|
|
298
|
+
"attempt_fence": self.attempt_fence,
|
|
299
|
+
"checkpoint_epoch": self.checkpoint_epoch,
|
|
300
|
+
"checkpoint_sequence": self.checkpoint_sequence,
|
|
301
|
+
"parent_checkpoint_digest": self.parent_checkpoint_digest,
|
|
302
|
+
"deadline": self.deadline,
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
def from_dict(cls, value: object) -> Self:
|
|
307
|
+
document = _require_exact(
|
|
308
|
+
value,
|
|
309
|
+
frozenset(
|
|
310
|
+
{
|
|
311
|
+
"schema_version",
|
|
312
|
+
"delivery_id",
|
|
313
|
+
"execution_id",
|
|
314
|
+
"run_id",
|
|
315
|
+
"segment_id",
|
|
316
|
+
"source_pin_sha256",
|
|
317
|
+
"attempt_id",
|
|
318
|
+
"attempt_generation",
|
|
319
|
+
"attempt_fence",
|
|
320
|
+
"checkpoint_epoch",
|
|
321
|
+
"checkpoint_sequence",
|
|
322
|
+
"parent_checkpoint_digest",
|
|
323
|
+
"deadline",
|
|
324
|
+
}
|
|
325
|
+
),
|
|
326
|
+
"delivery",
|
|
327
|
+
)
|
|
328
|
+
return cls(**document)
|
|
329
|
+
|
|
330
|
+
def require_manifest(self, manifest: IngestionManifestV1) -> None:
|
|
331
|
+
if manifest.run_id != self.run_id:
|
|
332
|
+
raise IngestionCorrupt(
|
|
333
|
+
"INGESTION_DELIVERY_RUN", "ingestion_manifest.run_id", "does not match delivery"
|
|
334
|
+
)
|
|
335
|
+
if manifest.source_pin.digest != self.source_pin_sha256:
|
|
336
|
+
raise IngestionCorrupt(
|
|
337
|
+
"INGESTION_DELIVERY_SOURCE",
|
|
338
|
+
"ingestion_manifest.source_pin",
|
|
339
|
+
"does not match delivery",
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@dataclass(frozen=True)
|
|
344
|
+
class IngestionDeliveryManifestV1:
|
|
345
|
+
"""The only persisted mutable document in a delivery-specific manifest namespace."""
|
|
346
|
+
|
|
347
|
+
delivery: IngestionDeliveryBindingV1
|
|
348
|
+
manifest: IngestionManifestV1
|
|
349
|
+
schema_version: str = INGESTION_DELIVERY_MANIFEST_SCHEMA
|
|
350
|
+
|
|
351
|
+
def __post_init__(self) -> None:
|
|
352
|
+
if self.schema_version != INGESTION_DELIVERY_MANIFEST_SCHEMA:
|
|
353
|
+
raise _error(
|
|
354
|
+
"INGESTION_DELIVERY_MANIFEST_SCHEMA",
|
|
355
|
+
"delivery_manifest.schema_version",
|
|
356
|
+
"is unsupported",
|
|
357
|
+
)
|
|
358
|
+
if not isinstance(self.delivery, IngestionDeliveryBindingV1):
|
|
359
|
+
raise _error("INGESTION_DELIVERY_BINDING", "delivery_manifest.delivery", "is invalid")
|
|
360
|
+
if not isinstance(self.manifest, IngestionManifestV1):
|
|
361
|
+
raise _error("INGESTION_MANIFEST", "delivery_manifest.manifest", "is invalid")
|
|
362
|
+
self.delivery.require_manifest(self.manifest)
|
|
363
|
+
|
|
364
|
+
def to_dict(self) -> dict[str, object]:
|
|
365
|
+
return {
|
|
366
|
+
"schema_version": self.schema_version,
|
|
367
|
+
"delivery": self.delivery.to_dict(),
|
|
368
|
+
"manifest": self.manifest.to_dict(),
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
@classmethod
|
|
372
|
+
def from_dict(cls, value: object) -> Self:
|
|
373
|
+
document = _require_exact(
|
|
374
|
+
value, frozenset({"schema_version", "delivery", "manifest"}), "delivery_manifest"
|
|
375
|
+
)
|
|
376
|
+
return cls(
|
|
377
|
+
schema_version=document["schema_version"],
|
|
378
|
+
delivery=IngestionDeliveryBindingV1.from_dict(document["delivery"]),
|
|
379
|
+
manifest=IngestionManifestV1.from_dict(document["manifest"]),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@dataclass(frozen=True)
|
|
384
|
+
class PriorManifestV1:
|
|
385
|
+
"""The predecessor's namespace plus an exact document reference, or nothing at all."""
|
|
386
|
+
|
|
387
|
+
prefix: str
|
|
388
|
+
manifest_ref: IngestionManifestReferenceV1
|
|
389
|
+
|
|
390
|
+
def __post_init__(self) -> None:
|
|
391
|
+
_require_prefix(self.prefix, "prior_manifest.prefix")
|
|
392
|
+
if not isinstance(self.manifest_ref, IngestionManifestReferenceV1):
|
|
393
|
+
raise _error("INGESTION_MANIFEST_REF", "prior_manifest.manifest_ref", "is invalid")
|
|
394
|
+
|
|
395
|
+
def to_dict(self) -> dict[str, object]:
|
|
396
|
+
return {"prefix": self.prefix, "manifest_ref": self.manifest_ref.to_dict()}
|
|
397
|
+
|
|
398
|
+
@classmethod
|
|
399
|
+
def from_dict(cls, value: object) -> Self:
|
|
400
|
+
document = _require_exact(value, frozenset({"prefix", "manifest_ref"}), "prior_manifest")
|
|
401
|
+
return cls(
|
|
402
|
+
prefix=document["prefix"],
|
|
403
|
+
manifest_ref=IngestionManifestReferenceV1.from_dict(document["manifest_ref"]),
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@dataclass(frozen=True)
|
|
408
|
+
class HostedIngestionDispatchV1:
|
|
409
|
+
"""One authenticated dispatch exchange result with an entirely closed schema."""
|
|
410
|
+
|
|
411
|
+
delivery: IngestionDeliveryBindingV1
|
|
412
|
+
source_pin: SourceVersionPinV1
|
|
413
|
+
continuation: PublicOffsetContinuationV1
|
|
414
|
+
manifest_prefix: str
|
|
415
|
+
chunk_prefix: str
|
|
416
|
+
prior_manifest: PriorManifestV1 | None
|
|
417
|
+
schema_version: str = HOSTED_INGESTION_DISPATCH_SCHEMA
|
|
418
|
+
|
|
419
|
+
def __post_init__(self) -> None:
|
|
420
|
+
if self.schema_version != HOSTED_INGESTION_DISPATCH_SCHEMA:
|
|
421
|
+
raise _error("INGESTION_DISPATCH_SCHEMA", "dispatch.schema_version", "is unsupported")
|
|
422
|
+
if not isinstance(self.delivery, IngestionDeliveryBindingV1):
|
|
423
|
+
raise _error("INGESTION_DELIVERY_BINDING", "dispatch.delivery", "is invalid")
|
|
424
|
+
if not isinstance(self.source_pin, SourceVersionPinV1):
|
|
425
|
+
raise _error("INGESTION_SOURCE_PIN", "dispatch.source_pin", "is invalid")
|
|
426
|
+
if self.source_pin.digest != self.delivery.source_pin_sha256:
|
|
427
|
+
raise _error(
|
|
428
|
+
"INGESTION_DELIVERY_SOURCE", "dispatch.source_pin", "does not match delivery"
|
|
429
|
+
)
|
|
430
|
+
if not isinstance(self.continuation, PublicOffsetContinuationV1):
|
|
431
|
+
raise _error("INGESTION_CONTINUATION", "dispatch.continuation", "is invalid")
|
|
432
|
+
_require_prefix(self.manifest_prefix, "dispatch.manifest_prefix")
|
|
433
|
+
_require_delivery_prefix(
|
|
434
|
+
self.manifest_prefix, "dispatch.manifest_prefix", self.delivery.delivery_id
|
|
435
|
+
)
|
|
436
|
+
_require_prefix(self.chunk_prefix, "dispatch.chunk_prefix")
|
|
437
|
+
if self.prior_manifest is not None and not isinstance(self.prior_manifest, PriorManifestV1):
|
|
438
|
+
raise _error("INGESTION_PRIOR_MANIFEST", "dispatch.prior_manifest", "is invalid")
|
|
439
|
+
if self.prior_manifest is not None:
|
|
440
|
+
self.prior_manifest.manifest_ref.require_for_prefix(
|
|
441
|
+
prefix=self.prior_manifest.prefix, run_id=self.delivery.run_id
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
def to_dict(self) -> dict[str, object]:
|
|
445
|
+
return {
|
|
446
|
+
"schema_version": self.schema_version,
|
|
447
|
+
"delivery": self.delivery.to_dict(),
|
|
448
|
+
"source_pin": self.source_pin.to_dict(),
|
|
449
|
+
"continuation": self.continuation.to_dict(),
|
|
450
|
+
"manifest_prefix": self.manifest_prefix,
|
|
451
|
+
"chunk_prefix": self.chunk_prefix,
|
|
452
|
+
"prior_manifest": None
|
|
453
|
+
if self.prior_manifest is None
|
|
454
|
+
else self.prior_manifest.to_dict(),
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
@classmethod
|
|
458
|
+
def from_dict(cls, value: object) -> Self:
|
|
459
|
+
document = _require_exact(
|
|
460
|
+
value,
|
|
461
|
+
frozenset(
|
|
462
|
+
{
|
|
463
|
+
"schema_version",
|
|
464
|
+
"delivery",
|
|
465
|
+
"source_pin",
|
|
466
|
+
"continuation",
|
|
467
|
+
"manifest_prefix",
|
|
468
|
+
"chunk_prefix",
|
|
469
|
+
"prior_manifest",
|
|
470
|
+
}
|
|
471
|
+
),
|
|
472
|
+
"dispatch",
|
|
473
|
+
)
|
|
474
|
+
prior = document["prior_manifest"]
|
|
475
|
+
if prior is not None and not isinstance(prior, dict):
|
|
476
|
+
raise _error(
|
|
477
|
+
"INGESTION_PRIOR_MANIFEST", "dispatch.prior_manifest", "must be null or object"
|
|
478
|
+
)
|
|
479
|
+
return cls(
|
|
480
|
+
schema_version=document["schema_version"],
|
|
481
|
+
delivery=IngestionDeliveryBindingV1.from_dict(document["delivery"]),
|
|
482
|
+
source_pin=SourceVersionPinV1.from_dict(document["source_pin"]),
|
|
483
|
+
continuation=PublicOffsetContinuationV1.from_dict(document["continuation"]),
|
|
484
|
+
manifest_prefix=document["manifest_prefix"],
|
|
485
|
+
chunk_prefix=document["chunk_prefix"],
|
|
486
|
+
prior_manifest=None if prior is None else PriorManifestV1.from_dict(prior),
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
__all__ = [
|
|
491
|
+
"HOSTED_INGESTION_DISPATCH_SCHEMA",
|
|
492
|
+
"INGESTION_DELIVERY_BINDING_SCHEMA",
|
|
493
|
+
"INGESTION_DELIVERY_MANIFEST_SCHEMA",
|
|
494
|
+
"INGESTION_MANIFEST_REF_SCHEMA",
|
|
495
|
+
"MAX_DELIVERY_DEADLINE_SECONDS",
|
|
496
|
+
"PUBLIC_OFFSET_CONTINUATION_SCHEMA",
|
|
497
|
+
"HostedIngestionDispatchV1",
|
|
498
|
+
"IngestionDeliveryBindingV1",
|
|
499
|
+
"IngestionDeliveryManifestV1",
|
|
500
|
+
"IngestionManifestReferenceV1",
|
|
501
|
+
"PriorManifestV1",
|
|
502
|
+
"PublicOffsetContinuationV1",
|
|
503
|
+
"parse_delivery_deadline",
|
|
504
|
+
]
|