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,259 @@
|
|
|
1
|
+
"""Bounded read-only query boundary over exact sealed Parquet bytes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from collections.abc import Mapping, Sequence
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import pyarrow as pa
|
|
12
|
+
import pyarrow.compute as pc
|
|
13
|
+
|
|
14
|
+
from mostlyright.data_harness.plan_graph import HARD_MAX_OUTPUT_ROWS
|
|
15
|
+
from mostlyright.data_harness.rowset import open_sealed_parquet, render_filtered_window
|
|
16
|
+
from mostlyright.data_harness.visual_run.observations import (
|
|
17
|
+
safe_column_for_display,
|
|
18
|
+
safe_for_display,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
MAX_QUERY_ROWS = 200
|
|
22
|
+
MAX_QUERY_BYTES = 1024 * 1024
|
|
23
|
+
MAX_PREDICATES = 8
|
|
24
|
+
QUERY_OPERATORS = frozenset({"eq", "ne", "lt", "le", "gt", "ge", "is_null", "is_not_null"})
|
|
25
|
+
QUERY_DISPLAY_DISPOSITIONS = frozenset({"full", "redacted", "aggregate_only", "schema_only"})
|
|
26
|
+
PUBLIC_RIGHTS = frozenset({"public_domain", "permissive_license"})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class QueryEvidence:
|
|
31
|
+
"""Exact verified evidence that authorizes and explains a row-query result."""
|
|
32
|
+
|
|
33
|
+
candidate_digest: str
|
|
34
|
+
table_sha256: str
|
|
35
|
+
display: str
|
|
36
|
+
rights_statuses: tuple[str, ...]
|
|
37
|
+
classifications: tuple[str, ...]
|
|
38
|
+
profiles: Mapping[str, Mapping[str, Any]]
|
|
39
|
+
findings: tuple[Mapping[str, Any], ...]
|
|
40
|
+
lineage: Mapping[str, Mapping[str, Any]]
|
|
41
|
+
evidence_sha256: Mapping[str, str]
|
|
42
|
+
|
|
43
|
+
def validate(self, parquet_raw: bytes, available: Sequence[str]) -> None:
|
|
44
|
+
digests = (self.candidate_digest, self.table_sha256, *self.evidence_sha256.values())
|
|
45
|
+
if any(
|
|
46
|
+
not isinstance(digest, str)
|
|
47
|
+
or len(digest) != 64
|
|
48
|
+
or any(character not in "0123456789abcdef" for character in digest)
|
|
49
|
+
for digest in digests
|
|
50
|
+
):
|
|
51
|
+
raise QueryError("QUERY_EVIDENCE", "verified evidence digests are invalid")
|
|
52
|
+
if hashlib.sha256(parquet_raw).hexdigest() != self.table_sha256:
|
|
53
|
+
raise QueryError("QUERY_EVIDENCE", "row bytes differ from verified evidence")
|
|
54
|
+
if self.display not in QUERY_DISPLAY_DISPOSITIONS:
|
|
55
|
+
raise QueryError("QUERY_EVIDENCE", "display disposition is invalid")
|
|
56
|
+
if (
|
|
57
|
+
self.display != "full"
|
|
58
|
+
or not self.rights_statuses
|
|
59
|
+
or any(status not in PUBLIC_RIGHTS for status in self.rights_statuses)
|
|
60
|
+
or not self.classifications
|
|
61
|
+
or any(classification != "public" for classification in self.classifications)
|
|
62
|
+
):
|
|
63
|
+
raise QueryError(
|
|
64
|
+
"QUERY_DISPLAY_POLICY", "verified source governance forbids row-level display"
|
|
65
|
+
)
|
|
66
|
+
if set(self.profiles) != set(available) or set(self.lineage) != set(available):
|
|
67
|
+
raise QueryError(
|
|
68
|
+
"QUERY_EVIDENCE", "verified profile or lineage does not cover the sealed schema"
|
|
69
|
+
)
|
|
70
|
+
if any(not safe_column_for_display(column) for column in available):
|
|
71
|
+
raise QueryError("QUERY_SENSITIVE_COLUMN", "sealed schema contains a secret coordinate")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class QueryError(ValueError):
|
|
75
|
+
def __init__(self, code: str, detail: str) -> None:
|
|
76
|
+
self.code = code
|
|
77
|
+
super().__init__(detail)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _request(
|
|
81
|
+
columns: Sequence[str], predicates: Sequence[Mapping[str, Any]], offset: int, limit: int
|
|
82
|
+
) -> None:
|
|
83
|
+
if (
|
|
84
|
+
not columns
|
|
85
|
+
or len(columns) > 256
|
|
86
|
+
or len(columns) != len(set(columns))
|
|
87
|
+
or any(not isinstance(column, str) for column in columns)
|
|
88
|
+
):
|
|
89
|
+
raise QueryError("QUERY_COLUMNS", "selected columns are invalid")
|
|
90
|
+
if isinstance(offset, bool) or not isinstance(offset, int) or offset < 0:
|
|
91
|
+
raise QueryError("QUERY_WINDOW", "offset is invalid")
|
|
92
|
+
if isinstance(limit, bool) or not isinstance(limit, int) or not 1 <= limit <= MAX_QUERY_ROWS:
|
|
93
|
+
raise QueryError("QUERY_WINDOW", "limit is invalid")
|
|
94
|
+
if len(predicates) > MAX_PREDICATES:
|
|
95
|
+
raise QueryError("QUERY_FILTER_BOUNDS", "too many predicates")
|
|
96
|
+
for predicate in predicates:
|
|
97
|
+
if not isinstance(predicate, Mapping) or set(predicate) not in (
|
|
98
|
+
{"column", "op"},
|
|
99
|
+
{"column", "op", "value"},
|
|
100
|
+
):
|
|
101
|
+
raise QueryError("QUERY_FILTER", "predicate fields are invalid")
|
|
102
|
+
if not isinstance(predicate["column"], str) or predicate["op"] not in QUERY_OPERATORS:
|
|
103
|
+
raise QueryError("QUERY_FILTER", "predicate coordinate is invalid")
|
|
104
|
+
nullary = predicate["op"] in {"is_null", "is_not_null"}
|
|
105
|
+
if nullary != ("value" not in predicate):
|
|
106
|
+
raise QueryError("QUERY_FILTER", "predicate value shape is invalid")
|
|
107
|
+
if "value" in predicate and type(predicate["value"]) not in {bool, int, float, str}:
|
|
108
|
+
raise QueryError("QUERY_FILTER", "predicate value is not a scalar")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def query_sealed_parquet(
|
|
112
|
+
parquet_raw: bytes,
|
|
113
|
+
*,
|
|
114
|
+
columns: Sequence[str],
|
|
115
|
+
predicates: Sequence[Mapping[str, Any]] = (),
|
|
116
|
+
offset: int = 0,
|
|
117
|
+
limit: int = 50,
|
|
118
|
+
evidence: QueryEvidence,
|
|
119
|
+
) -> dict[str, Any]:
|
|
120
|
+
"""Return one deterministic physical-order window; never SQL or arbitrary expressions."""
|
|
121
|
+
|
|
122
|
+
_request(columns, predicates, offset, limit)
|
|
123
|
+
parquet, available, total = open_sealed_parquet(
|
|
124
|
+
parquet_raw,
|
|
125
|
+
max_columns=256,
|
|
126
|
+
max_rows=HARD_MAX_OUTPUT_ROWS,
|
|
127
|
+
max_uncompressed_bytes=256 * 1024 * 1024,
|
|
128
|
+
)
|
|
129
|
+
evidence.validate(parquet_raw, available)
|
|
130
|
+
unknown = sorted(({*columns} | {str(item["column"]) for item in predicates}) - set(available))
|
|
131
|
+
if unknown:
|
|
132
|
+
raise QueryError("QUERY_COLUMN_UNKNOWN", f"columns are absent: {unknown}")
|
|
133
|
+
if any(
|
|
134
|
+
not safe_column_for_display(column)
|
|
135
|
+
for column in (*columns, *(str(item["column"]) for item in predicates))
|
|
136
|
+
):
|
|
137
|
+
raise QueryError("QUERY_SENSITIVE_COLUMN", "selected column is a secret coordinate")
|
|
138
|
+
if any(
|
|
139
|
+
"value" in predicate and not safe_for_display(predicate["value"])
|
|
140
|
+
for predicate in predicates
|
|
141
|
+
):
|
|
142
|
+
raise QueryError("QUERY_SENSITIVE_VALUE", "filter contains credential-bearing text")
|
|
143
|
+
|
|
144
|
+
inspected = {*columns, *(str(item["column"]) for item in predicates)}
|
|
145
|
+
for batch in parquet.iter_batches(batch_size=1_024, columns=sorted(inspected)):
|
|
146
|
+
for column in inspected:
|
|
147
|
+
array = batch.column(batch.schema.get_field_index(column))
|
|
148
|
+
if any(
|
|
149
|
+
scalar.is_valid
|
|
150
|
+
and isinstance(scalar.as_py(), str)
|
|
151
|
+
and not safe_for_display(scalar.as_py())
|
|
152
|
+
for scalar in array
|
|
153
|
+
):
|
|
154
|
+
raise QueryError(
|
|
155
|
+
"QUERY_SENSITIVE_VALUE", "selected data contains credential-bearing text"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def select(batch: pa.RecordBatch) -> pa.RecordBatch | pa.Table:
|
|
159
|
+
mask: pa.Array | pa.ChunkedArray | None = None
|
|
160
|
+
for predicate in predicates:
|
|
161
|
+
array = batch.column(batch.schema.get_field_index(str(predicate["column"])))
|
|
162
|
+
op = str(predicate["op"])
|
|
163
|
+
if op == "is_null":
|
|
164
|
+
condition = pc.is_null(array)
|
|
165
|
+
elif op == "is_not_null":
|
|
166
|
+
condition = pc.invert(pc.is_null(array))
|
|
167
|
+
else:
|
|
168
|
+
functions = {
|
|
169
|
+
"eq": pc.equal,
|
|
170
|
+
"ne": pc.not_equal,
|
|
171
|
+
"lt": pc.less,
|
|
172
|
+
"le": pc.less_equal,
|
|
173
|
+
"gt": pc.greater,
|
|
174
|
+
"ge": pc.greater_equal,
|
|
175
|
+
}
|
|
176
|
+
try:
|
|
177
|
+
condition = functions[op](array, predicate["value"])
|
|
178
|
+
except (ArrowInvalid, ArrowNotImplementedError) as exc: # type: ignore[name-defined]
|
|
179
|
+
raise QueryError(
|
|
180
|
+
"QUERY_FILTER_TYPE", "filter value does not match column"
|
|
181
|
+
) from exc
|
|
182
|
+
mask = condition if mask is None else pc.and_kleene(mask, condition)
|
|
183
|
+
retained: pa.RecordBatch | pa.Table = batch if mask is None else batch.filter(mask)
|
|
184
|
+
return retained.select(columns)
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
matched, rows = render_filtered_window(
|
|
188
|
+
parquet,
|
|
189
|
+
columns,
|
|
190
|
+
select=select,
|
|
191
|
+
offset=offset,
|
|
192
|
+
limit=limit,
|
|
193
|
+
exact_match_count=total if not predicates else None,
|
|
194
|
+
)
|
|
195
|
+
except QueryError:
|
|
196
|
+
raise
|
|
197
|
+
payload = {
|
|
198
|
+
"schema_version": "sealed-row-query.v1",
|
|
199
|
+
"columns": list(columns),
|
|
200
|
+
"total_row_count": total,
|
|
201
|
+
"matched_row_count": matched,
|
|
202
|
+
"offset": offset,
|
|
203
|
+
"limit": limit,
|
|
204
|
+
"rows": rows,
|
|
205
|
+
"truncated": offset + len(rows) < matched,
|
|
206
|
+
"evidence": {
|
|
207
|
+
"candidate_digest": evidence.candidate_digest,
|
|
208
|
+
"table_sha256": evidence.table_sha256,
|
|
209
|
+
"display": evidence.display,
|
|
210
|
+
"rights_statuses": list(evidence.rights_statuses),
|
|
211
|
+
"classifications": list(evidence.classifications),
|
|
212
|
+
"profiles": [dict(evidence.profiles[column]) for column in columns],
|
|
213
|
+
"findings": [
|
|
214
|
+
dict(finding)
|
|
215
|
+
for finding in evidence.findings
|
|
216
|
+
if finding.get("column") in {None, *columns}
|
|
217
|
+
],
|
|
218
|
+
"lineage": [dict(evidence.lineage[column]) for column in columns],
|
|
219
|
+
"member_sha256": dict(evidence.evidence_sha256),
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
while (
|
|
223
|
+
len(json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode())
|
|
224
|
+
> MAX_QUERY_BYTES
|
|
225
|
+
):
|
|
226
|
+
if not payload["rows"]:
|
|
227
|
+
raise QueryError("QUERY_RESPONSE_BOUNDS", "query metadata exceeds response budget")
|
|
228
|
+
payload["rows"] = payload["rows"][:-1]
|
|
229
|
+
payload["truncated"] = True
|
|
230
|
+
return payload
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def sealed_parquet_columns(parquet_raw: bytes, *, evidence: QueryEvidence) -> tuple[str, ...]:
|
|
234
|
+
"""Return the bounded sealed schema for the viewer's initial explorer request."""
|
|
235
|
+
|
|
236
|
+
_parquet, available, _total = open_sealed_parquet(
|
|
237
|
+
parquet_raw,
|
|
238
|
+
max_columns=256,
|
|
239
|
+
max_rows=HARD_MAX_OUTPUT_ROWS,
|
|
240
|
+
max_uncompressed_bytes=256 * 1024 * 1024,
|
|
241
|
+
)
|
|
242
|
+
evidence.validate(parquet_raw, available)
|
|
243
|
+
return tuple(available)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# Local aliases keep pyarrow exceptions out of the public contract.
|
|
247
|
+
ArrowInvalid = pa.ArrowInvalid
|
|
248
|
+
ArrowNotImplementedError = pa.ArrowNotImplementedError
|
|
249
|
+
|
|
250
|
+
__all__ = [
|
|
251
|
+
"MAX_PREDICATES",
|
|
252
|
+
"MAX_QUERY_BYTES",
|
|
253
|
+
"MAX_QUERY_ROWS",
|
|
254
|
+
"QUERY_OPERATORS",
|
|
255
|
+
"QueryError",
|
|
256
|
+
"QueryEvidence",
|
|
257
|
+
"query_sealed_parquet",
|
|
258
|
+
"sealed_parquet_columns",
|
|
259
|
+
]
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""Pure deterministic reduction of visual-run events."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass, replace
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
|
|
9
|
+
from mostlyright.data_harness.visual_run.contracts import (
|
|
10
|
+
EVENT_REGISTRY,
|
|
11
|
+
ArtifactReference,
|
|
12
|
+
TerminalError,
|
|
13
|
+
VisualRunError,
|
|
14
|
+
VisualRunEvent,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class CellState:
|
|
20
|
+
cell_id: str
|
|
21
|
+
cell_type: str
|
|
22
|
+
version: int
|
|
23
|
+
phase: str
|
|
24
|
+
stage: str
|
|
25
|
+
status: str
|
|
26
|
+
source_length: int = 0
|
|
27
|
+
source: ArtifactReference | None = None
|
|
28
|
+
execution_count: int | None = None
|
|
29
|
+
outputs: tuple[ArtifactReference, ...] = ()
|
|
30
|
+
error: TerminalError | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ObjectState:
|
|
35
|
+
"""Complete current state for every non-cell registry object family."""
|
|
36
|
+
|
|
37
|
+
object_id: str
|
|
38
|
+
kind: str
|
|
39
|
+
version: int
|
|
40
|
+
phase: str
|
|
41
|
+
stage: str
|
|
42
|
+
status: str
|
|
43
|
+
payload: Mapping[str, object]
|
|
44
|
+
artifact: ArtifactReference | None = None
|
|
45
|
+
error: TerminalError | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class ReducedRun:
|
|
50
|
+
run_id: str | None = None
|
|
51
|
+
cursor: int = 0
|
|
52
|
+
lifecycle: str = "waiting"
|
|
53
|
+
active_object_id: str | None = None
|
|
54
|
+
cells: Mapping[str, CellState] = MappingProxyType({})
|
|
55
|
+
observations: tuple[ArtifactReference, ...] = ()
|
|
56
|
+
quality: tuple[int, int] | None = None
|
|
57
|
+
candidate_digest: str | None = None
|
|
58
|
+
table_ready: bool = False
|
|
59
|
+
terminal_error: TerminalError | None = None
|
|
60
|
+
terminal: bool = False
|
|
61
|
+
object_versions: Mapping[str, int] = MappingProxyType({})
|
|
62
|
+
object_kinds: Mapping[str, str] = MappingProxyType({})
|
|
63
|
+
objects: Mapping[str, ObjectState] = MappingProxyType({})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _fail(code: str, message: str) -> None:
|
|
67
|
+
raise VisualRunError(code, message)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def apply_event(state: ReducedRun, event: VisualRunEvent) -> ReducedRun:
|
|
71
|
+
if event.sequence != state.cursor + 1:
|
|
72
|
+
_fail("VISUAL_RUN_SEQUENCE_GAP", "reducer requires the next durable sequence")
|
|
73
|
+
if state.run_id is not None and event.run_id != state.run_id:
|
|
74
|
+
_fail("VISUAL_RUN_RUN_MISMATCH", "event belongs to another run")
|
|
75
|
+
if state.terminal:
|
|
76
|
+
_fail("VISUAL_RUN_TERMINAL_APPEND", "events cannot follow a terminal run event")
|
|
77
|
+
cells = dict(state.cells)
|
|
78
|
+
observations = state.observations
|
|
79
|
+
lifecycle = state.lifecycle
|
|
80
|
+
active = state.active_object_id
|
|
81
|
+
quality = state.quality
|
|
82
|
+
candidate_digest = state.candidate_digest
|
|
83
|
+
table_ready = state.table_ready
|
|
84
|
+
terminal_error = state.terminal_error
|
|
85
|
+
terminal = state.terminal
|
|
86
|
+
object_versions = dict(state.object_versions)
|
|
87
|
+
object_kinds = dict(state.object_kinds)
|
|
88
|
+
objects = dict(state.objects)
|
|
89
|
+
|
|
90
|
+
event_type = event.event_type
|
|
91
|
+
spec = EVENT_REGISTRY[event_type]
|
|
92
|
+
event_family = event_type.split(".", 1)[0]
|
|
93
|
+
current_version = object_versions.get(event.object_id)
|
|
94
|
+
if spec.object_rule == "create":
|
|
95
|
+
if current_version is not None:
|
|
96
|
+
_fail("VISUAL_RUN_OBJECT_EXISTS", "object creation was already recorded")
|
|
97
|
+
if event.previous_object_version is not None or event.object_version != 1:
|
|
98
|
+
_fail("VISUAL_RUN_OBJECT_VERSION", "object creation must start at version one")
|
|
99
|
+
else:
|
|
100
|
+
if current_version is None:
|
|
101
|
+
_fail("VISUAL_RUN_OBJECT_MISSING", "object update has no creation event")
|
|
102
|
+
if (
|
|
103
|
+
event.previous_object_version != current_version
|
|
104
|
+
or event.object_version != current_version + 1
|
|
105
|
+
):
|
|
106
|
+
_fail("VISUAL_RUN_OBJECT_VERSION", "object update does not follow current version")
|
|
107
|
+
if event_type != "run.started" and state.run_id is None:
|
|
108
|
+
_fail("VISUAL_RUN_TRANSITION", "run must start before other events")
|
|
109
|
+
established_kind = object_kinds.get(event.object_id)
|
|
110
|
+
compatible_kinds = {established_kind}
|
|
111
|
+
if established_kind == "transformation":
|
|
112
|
+
compatible_kinds.add("stage")
|
|
113
|
+
if current_version is not None and event_family not in compatible_kinds:
|
|
114
|
+
_fail("VISUAL_RUN_OBJECT_KIND", "object update changes registry family")
|
|
115
|
+
if event_type == "run.started":
|
|
116
|
+
lifecycle = "researching"
|
|
117
|
+
elif event_type in {"run.completed", "run.failed", "run.interrupted"}:
|
|
118
|
+
lifecycle = event.status or "failed"
|
|
119
|
+
terminal_error = event.error
|
|
120
|
+
terminal = True
|
|
121
|
+
active = None
|
|
122
|
+
elif event_type == "phase.started":
|
|
123
|
+
lifecycle = event.phase
|
|
124
|
+
terminal_error = None
|
|
125
|
+
active = event.object_id
|
|
126
|
+
elif event_type == "phase.completed":
|
|
127
|
+
active = None
|
|
128
|
+
elif event_type == "phase.failed":
|
|
129
|
+
lifecycle = "failed"
|
|
130
|
+
terminal_error = event.error
|
|
131
|
+
active = None
|
|
132
|
+
terminal_error = event.error
|
|
133
|
+
elif event_type == "cell.created":
|
|
134
|
+
cells[event.object_id] = CellState(
|
|
135
|
+
event.object_id,
|
|
136
|
+
str(event.payload["cell_type"]),
|
|
137
|
+
event.object_version,
|
|
138
|
+
event.phase,
|
|
139
|
+
event.stage,
|
|
140
|
+
"writing",
|
|
141
|
+
)
|
|
142
|
+
active = event.object_id
|
|
143
|
+
elif event_type.startswith("cell."):
|
|
144
|
+
moved = False
|
|
145
|
+
cell = cells.get(event.object_id)
|
|
146
|
+
if cell is None:
|
|
147
|
+
_fail("VISUAL_RUN_OBJECT_MISSING", "cell update has no creation event")
|
|
148
|
+
if cell.status in {"complete", "failed"} and event_type not in {
|
|
149
|
+
"cell.source_revised",
|
|
150
|
+
"cell.moved",
|
|
151
|
+
}:
|
|
152
|
+
_fail("VISUAL_RUN_OBJECT_TERMINAL", "terminal cell cannot be changed")
|
|
153
|
+
if event_type in {"cell.source_extended", "cell.source_revised"}:
|
|
154
|
+
length = int(event.payload["source_length"])
|
|
155
|
+
if event_type == "cell.source_extended" and length < cell.source_length:
|
|
156
|
+
_fail("VISUAL_RUN_TRANSITION", "source length cannot shrink")
|
|
157
|
+
if event.artifact is None:
|
|
158
|
+
_fail("VISUAL_RUN_ARTIFACT_REQUIRED", "source update requires artifact")
|
|
159
|
+
cell = replace(
|
|
160
|
+
cell,
|
|
161
|
+
source_length=length,
|
|
162
|
+
source=event.artifact,
|
|
163
|
+
status="writing",
|
|
164
|
+
execution_count=None,
|
|
165
|
+
outputs=(),
|
|
166
|
+
error=None,
|
|
167
|
+
)
|
|
168
|
+
active = event.object_id
|
|
169
|
+
elif event_type == "cell.moved":
|
|
170
|
+
after = str(event.payload["after_cell_id"])
|
|
171
|
+
if after == event.object_id or after not in cells:
|
|
172
|
+
_fail("VISUAL_RUN_TRANSITION", "cell move anchor is invalid")
|
|
173
|
+
moving = replace(cells.pop(event.object_id), version=event.object_version)
|
|
174
|
+
reordered: dict[str, CellState] = {}
|
|
175
|
+
for identifier, existing in cells.items():
|
|
176
|
+
reordered[identifier] = existing
|
|
177
|
+
if identifier == after:
|
|
178
|
+
reordered[event.object_id] = moving
|
|
179
|
+
cells = reordered
|
|
180
|
+
cell = moving
|
|
181
|
+
moved = True
|
|
182
|
+
elif event_type == "cell.execution_started":
|
|
183
|
+
cell = replace(
|
|
184
|
+
cell, execution_count=int(event.payload["execution_count"]), status="executing"
|
|
185
|
+
)
|
|
186
|
+
elif event_type == "cell.output_appended":
|
|
187
|
+
if event.artifact is None:
|
|
188
|
+
_fail("VISUAL_RUN_ARTIFACT_REQUIRED", "output requires artifact")
|
|
189
|
+
index = int(event.payload["output_index"])
|
|
190
|
+
if index != len(cell.outputs):
|
|
191
|
+
_fail("VISUAL_RUN_TRANSITION", "appended output index must be next")
|
|
192
|
+
cell = replace(cell, outputs=(*cell.outputs, event.artifact), status="executing")
|
|
193
|
+
elif event_type == "cell.output_replaced":
|
|
194
|
+
if event.artifact is None:
|
|
195
|
+
_fail("VISUAL_RUN_ARTIFACT_REQUIRED", "output requires artifact")
|
|
196
|
+
index = int(event.payload["output_index"])
|
|
197
|
+
if not 0 <= index < len(cell.outputs):
|
|
198
|
+
_fail("VISUAL_RUN_TRANSITION", "replacement output index is absent")
|
|
199
|
+
outputs = list(cell.outputs)
|
|
200
|
+
outputs[index] = event.artifact
|
|
201
|
+
cell = replace(cell, outputs=tuple(outputs), status="executing")
|
|
202
|
+
elif event_type == "cell.completed":
|
|
203
|
+
cell = replace(cell, status="complete")
|
|
204
|
+
active = None
|
|
205
|
+
elif event_type == "cell.failed":
|
|
206
|
+
cell = replace(cell, status="failed", error=event.error)
|
|
207
|
+
active = None
|
|
208
|
+
if not moved:
|
|
209
|
+
cells[event.object_id] = replace(cell, version=event.object_version)
|
|
210
|
+
elif event_type in {
|
|
211
|
+
"source.schema_observed",
|
|
212
|
+
"source.observation_ready",
|
|
213
|
+
"stage.observation_ready",
|
|
214
|
+
}:
|
|
215
|
+
if event.artifact is not None:
|
|
216
|
+
observations = (*observations, event.artifact)
|
|
217
|
+
elif event_type == "quality.completed":
|
|
218
|
+
quality = (int(event.payload["checks_passed"]), int(event.payload["checks_total"]))
|
|
219
|
+
elif event_type == "build.sealed":
|
|
220
|
+
candidate_digest = str(event.payload["candidate_digest"])
|
|
221
|
+
lifecycle = "building"
|
|
222
|
+
elif event_type == "build.verified":
|
|
223
|
+
digest = str(event.payload["candidate_digest"])
|
|
224
|
+
if candidate_digest is None:
|
|
225
|
+
_fail("VISUAL_RUN_TRANSITION", "build must be sealed before verification")
|
|
226
|
+
if digest != candidate_digest:
|
|
227
|
+
_fail("VISUAL_RUN_BUILD_MISMATCH", "verified digest differs from sealed digest")
|
|
228
|
+
candidate_digest = digest
|
|
229
|
+
table_ready = True
|
|
230
|
+
lifecycle = "table-ready"
|
|
231
|
+
|
|
232
|
+
if event.status is not None:
|
|
233
|
+
status = event.status
|
|
234
|
+
elif event_type.endswith(".failed"):
|
|
235
|
+
status = "failed"
|
|
236
|
+
elif event_type == "source.rejected":
|
|
237
|
+
status = "rejected"
|
|
238
|
+
elif event_type.endswith((".completed", ".verified", ".written", ".materialized")) or (
|
|
239
|
+
event_type.endswith((".observation_ready", ".schema_observed"))
|
|
240
|
+
):
|
|
241
|
+
status = "complete"
|
|
242
|
+
else:
|
|
243
|
+
status = "active"
|
|
244
|
+
objects[event.object_id] = ObjectState(
|
|
245
|
+
object_id=event.object_id,
|
|
246
|
+
kind=established_kind or event_family,
|
|
247
|
+
version=event.object_version,
|
|
248
|
+
phase=event.phase,
|
|
249
|
+
stage=event.stage,
|
|
250
|
+
status=status,
|
|
251
|
+
payload=event.payload,
|
|
252
|
+
artifact=event.artifact,
|
|
253
|
+
error=event.error,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
object_versions[event.object_id] = event.object_version
|
|
257
|
+
object_kinds.setdefault(event.object_id, event_family)
|
|
258
|
+
return ReducedRun(
|
|
259
|
+
run_id=event.run_id,
|
|
260
|
+
cursor=event.sequence,
|
|
261
|
+
lifecycle=lifecycle,
|
|
262
|
+
active_object_id=active,
|
|
263
|
+
cells=MappingProxyType(cells),
|
|
264
|
+
observations=observations,
|
|
265
|
+
quality=quality,
|
|
266
|
+
candidate_digest=candidate_digest,
|
|
267
|
+
table_ready=table_ready,
|
|
268
|
+
terminal_error=terminal_error,
|
|
269
|
+
terminal=terminal,
|
|
270
|
+
object_versions=MappingProxyType(object_versions),
|
|
271
|
+
object_kinds=MappingProxyType(object_kinds),
|
|
272
|
+
objects=MappingProxyType(objects),
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def reduce_events(events: tuple[VisualRunEvent, ...] | list[VisualRunEvent]) -> ReducedRun:
|
|
277
|
+
state = ReducedRun()
|
|
278
|
+
for event in events:
|
|
279
|
+
state = apply_event(state, event)
|
|
280
|
+
return state
|