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,351 @@
|
|
|
1
|
+
"""Linux image smoke for the real crawler seccomp, normalizer exec boundary, and decoder.
|
|
2
|
+
|
|
3
|
+
Two things are checked here that cannot be checked anywhere else, because both are properties
|
|
4
|
+
of the confined child rather than of the code that runs in it.
|
|
5
|
+
|
|
6
|
+
The first is the crawler's own boundary: normalization happens in a child that can reach no
|
|
7
|
+
network, and a deliberately malicious probe confirms every escape it might try returns EPERM.
|
|
8
|
+
|
|
9
|
+
The second is the weather decoder. The filter installed in that child denies thread
|
|
10
|
+
creation outright -- ``clone``, ``fork``, ``vfork`` and ``clone3`` are all in
|
|
11
|
+
``_PROCESS_ESCAPE_SYSCALLS_X86_64`` -- so a compiled decoder that spawned a worker pool would not
|
|
12
|
+
run slowly there, it would die. That makes this the one environment where "single-threaded" is
|
|
13
|
+
enforced rather than merely expected, which is why the claim is tested here and not in a unit
|
|
14
|
+
test that would pass on a decoder with a thread pool in it.
|
|
15
|
+
|
|
16
|
+
The thread count is sampled **during** the decode, from the parent, while the child decodes in a
|
|
17
|
+
loop. A library that spawned a pool for the duration of a call and joined it afterwards would
|
|
18
|
+
pass a before-and-after check and die under the filter, so a before-and-after check would be the
|
|
19
|
+
wrong check to write down as green.
|
|
20
|
+
|
|
21
|
+
The decoded levels are compared against digests that were computed on both target platforms and
|
|
22
|
+
found identical, so this doubles as the cross-platform determinism check for the decoder: a wrong
|
|
23
|
+
answer here fails the image, exactly as a wrong warm-up answer halts a refresh.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import subprocess
|
|
29
|
+
import sys
|
|
30
|
+
import time
|
|
31
|
+
from types import SimpleNamespace
|
|
32
|
+
|
|
33
|
+
from mostlyright.data_harness.canonical import parse_canonical_json, sha256_bytes
|
|
34
|
+
from mostlyright.data_harness.hosted_crawler import encode_normalization_input
|
|
35
|
+
from mostlyright.data_harness.hosted_crawler_job import (
|
|
36
|
+
_disable_process_dumpability,
|
|
37
|
+
_install_networkless_seccomp,
|
|
38
|
+
_run_crawler_child,
|
|
39
|
+
)
|
|
40
|
+
from mostlyright.data_harness.hosted_crawler_protocol import (
|
|
41
|
+
OPENLIGADB_EGRESS_POLICY_ATTESTATION,
|
|
42
|
+
HostedCrawlerRequest,
|
|
43
|
+
parse_crawler_result,
|
|
44
|
+
)
|
|
45
|
+
from mostlyright.data_harness.readers.grib2 import MESSAGES_ROOT
|
|
46
|
+
|
|
47
|
+
_RAW_FIXTURE = b"""[{
|
|
48
|
+
"matchID":1,
|
|
49
|
+
"matchDateTimeUTC":"2025-08-22T18:30:00Z",
|
|
50
|
+
"lastUpdateDateTime":"2025-08-23T01:00:54.147",
|
|
51
|
+
"leagueShortcut":"bl1",
|
|
52
|
+
"leagueSeason":2025,
|
|
53
|
+
"group":{"groupOrderID":1},
|
|
54
|
+
"team1":{"teamId":40,"teamName":"FC Bayern Muenchen"},
|
|
55
|
+
"team2":{"teamId":1635,"teamName":"RB Leipzig"},
|
|
56
|
+
"matchIsFinished":true,
|
|
57
|
+
"matchResults":[{
|
|
58
|
+
"resultTypeID":2,
|
|
59
|
+
"resultOrderID":1,
|
|
60
|
+
"resultID":1,
|
|
61
|
+
"pointsTeam1":6,
|
|
62
|
+
"pointsTeam2":0
|
|
63
|
+
}]
|
|
64
|
+
}]"""
|
|
65
|
+
|
|
66
|
+
_MALICIOUS_PROBE = r"""
|
|
67
|
+
import ctypes
|
|
68
|
+
import errno
|
|
69
|
+
import os
|
|
70
|
+
|
|
71
|
+
links = []
|
|
72
|
+
for name in os.listdir("/proc/self/fd"):
|
|
73
|
+
try:
|
|
74
|
+
links.append(os.readlink("/proc/self/fd/" + name))
|
|
75
|
+
except OSError:
|
|
76
|
+
pass
|
|
77
|
+
if any(link.startswith("socket:[") for link in links):
|
|
78
|
+
raise SystemExit(20)
|
|
79
|
+
status = open("/proc/self/status", encoding="ascii").read()
|
|
80
|
+
if "NoNewPrivs:\t1" not in status or "Seccomp:\t2" not in status:
|
|
81
|
+
raise SystemExit(21)
|
|
82
|
+
try:
|
|
83
|
+
open(f"/proc/{os.getppid()}/environ", "rb").read()
|
|
84
|
+
except PermissionError:
|
|
85
|
+
pass
|
|
86
|
+
else:
|
|
87
|
+
raise SystemExit(22)
|
|
88
|
+
libc = ctypes.CDLL(None, use_errno=True)
|
|
89
|
+
for number, arguments in (
|
|
90
|
+
(41, (2, 1, 0)),
|
|
91
|
+
(42, (-1, 0, 0)),
|
|
92
|
+
(0x40000000 | 41, (2, 1, 0)),
|
|
93
|
+
(0x40000000 | 42, (-1, 0, 0)),
|
|
94
|
+
(56, (0, 0, 0, 0, 0)),
|
|
95
|
+
(101, (0, 0, 0, 0)),
|
|
96
|
+
(310, (os.getppid(), 0, 0, 0, 0, 0)),
|
|
97
|
+
(434, (os.getppid(), 0)),
|
|
98
|
+
):
|
|
99
|
+
ctypes.set_errno(0)
|
|
100
|
+
outcome = libc.syscall(number, *arguments)
|
|
101
|
+
if outcome != -1 or ctypes.get_errno() != errno.EPERM:
|
|
102
|
+
raise SystemExit(23)
|
|
103
|
+
print("networkless-seccomp-ok")
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Every admitted template pair, with the point count and the digest of the levels the decoder
|
|
108
|
+
# returns for it. The digests were computed on macOS arm64 and on Linux x86_64 and are identical:
|
|
109
|
+
# the levels are exact integers recovered from a deterministic bit unpacking, so they have no
|
|
110
|
+
# licence to differ, and pinning them here is what turns that from an expectation into a check.
|
|
111
|
+
_DECODER_SAMPLES = (
|
|
112
|
+
(
|
|
113
|
+
"gfs_2m_temperature/gfs-2m-temperature.grib2",
|
|
114
|
+
0,
|
|
115
|
+
3,
|
|
116
|
+
65160,
|
|
117
|
+
"e6597dbf68fcf31ad983491122484bf6391d5ace74daf56cb5fa76d1c9b217db",
|
|
118
|
+
),
|
|
119
|
+
(
|
|
120
|
+
"hrrr_2m_temperature/hrrr-2m-temperature.grib2",
|
|
121
|
+
30,
|
|
122
|
+
3,
|
|
123
|
+
1905141,
|
|
124
|
+
"2e2a2468d687c082c4ec00341e2dd0822d77b1be08246272279ad6ea7e8a7307",
|
|
125
|
+
),
|
|
126
|
+
(
|
|
127
|
+
"hrrr_categorical_rain/hrrr-categorical-rain.grib2",
|
|
128
|
+
30,
|
|
129
|
+
0,
|
|
130
|
+
1905141,
|
|
131
|
+
"b0379dce013d12446af5c48bf41b14a3f7571c8c5e20cecacd48f2026881d61c",
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Environment variables that a library with a thread pool would expect to be told to stand down
|
|
136
|
+
# with. None of them is set here, and the probe refuses to run if one is. A decoder that is
|
|
137
|
+
# single-threaded only when configured to be is single-threaded nowhere it is not configured, and
|
|
138
|
+
# recording "single-threaded" on the strength of a variable somebody set would be recording the
|
|
139
|
+
# variable rather than the property.
|
|
140
|
+
_FORCING_VARIABLES = (
|
|
141
|
+
"RAYON_NUM_THREADS",
|
|
142
|
+
"OMP_NUM_THREADS",
|
|
143
|
+
"MKL_NUM_THREADS",
|
|
144
|
+
"OPENBLAS_NUM_THREADS",
|
|
145
|
+
"NUMEXPR_NUM_THREADS",
|
|
146
|
+
"TOKIO_WORKER_THREADS",
|
|
147
|
+
"MOSTLYRIGHT_GRIB_THREADS",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
_DECODER_SECONDS = 1.5
|
|
151
|
+
_DECODER_SAMPLE_INTERVAL = 0.005
|
|
152
|
+
_DECODER_LEAST_SAMPLES = 20
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _decoder_probe() -> str:
|
|
156
|
+
"""The child's whole program, with the sample paths resolved by the parent.
|
|
157
|
+
|
|
158
|
+
The paths are resolved here rather than imported there on purpose, and the reason is a
|
|
159
|
+
measurement. Importing the decoder alone leaves the process on one thread. Importing this
|
|
160
|
+
harness package takes it to nine, because the analytics engines it loads start pools of their
|
|
161
|
+
own at import time -- nothing to do with the decoder, and pre-existing, but enough to drown
|
|
162
|
+
the signal this probe exists to read. So the child imports the decoder and the standard
|
|
163
|
+
library, and nothing else, and the thread count it reports is a statement about the decoder.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
resolved = tuple(
|
|
167
|
+
(str((MESSAGES_ROOT / relative).resolve()), grid, packing, count, digest)
|
|
168
|
+
for relative, grid, packing, count, digest in _DECODER_SAMPLES
|
|
169
|
+
)
|
|
170
|
+
return f"""
|
|
171
|
+
import hashlib
|
|
172
|
+
import os
|
|
173
|
+
import sys
|
|
174
|
+
import time
|
|
175
|
+
|
|
176
|
+
for name in {_FORCING_VARIABLES!r}:
|
|
177
|
+
if name in os.environ:
|
|
178
|
+
raise SystemExit(40)
|
|
179
|
+
|
|
180
|
+
status = open("/proc/self/status", encoding="ascii").read()
|
|
181
|
+
if "NoNewPrivs:\\t1" not in status or "Seccomp:\\t2" not in status:
|
|
182
|
+
raise SystemExit(41)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def threads():
|
|
186
|
+
for line in open("/proc/self/status", encoding="ascii"):
|
|
187
|
+
if line.startswith("Threads:"):
|
|
188
|
+
return int(line.split()[1])
|
|
189
|
+
raise SystemExit(42)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if threads() != 1:
|
|
193
|
+
raise SystemExit(43)
|
|
194
|
+
|
|
195
|
+
import mostlyright_grib
|
|
196
|
+
|
|
197
|
+
if threads() != 1:
|
|
198
|
+
raise SystemExit(44)
|
|
199
|
+
|
|
200
|
+
samples = []
|
|
201
|
+
for path, grid, packing, count, digest in {resolved!r}:
|
|
202
|
+
samples.append((open(path, "rb").read(), grid, packing, count, digest))
|
|
203
|
+
|
|
204
|
+
if threads() != 1:
|
|
205
|
+
raise SystemExit(48)
|
|
206
|
+
|
|
207
|
+
sys.stdout.write("decoder-ready\\n")
|
|
208
|
+
sys.stdout.flush()
|
|
209
|
+
|
|
210
|
+
rounds = 0
|
|
211
|
+
deadline = time.monotonic() + {_DECODER_SECONDS!r}
|
|
212
|
+
while True:
|
|
213
|
+
for content, grid, packing, count, digest in samples:
|
|
214
|
+
unpacked = mostlyright_grib.unpack(
|
|
215
|
+
content, grid_template=grid, packing_template=packing
|
|
216
|
+
)
|
|
217
|
+
if unpacked.point_count != count:
|
|
218
|
+
raise SystemExit(45)
|
|
219
|
+
if hashlib.sha256(memoryview(unpacked.levels).cast("B")).hexdigest() != digest:
|
|
220
|
+
raise SystemExit(46)
|
|
221
|
+
rounds += 1
|
|
222
|
+
if time.monotonic() >= deadline:
|
|
223
|
+
break
|
|
224
|
+
|
|
225
|
+
if threads() != 1:
|
|
226
|
+
raise SystemExit(47)
|
|
227
|
+
|
|
228
|
+
sys.stdout.write("decoder-ok %d %s\\n" % (rounds, mostlyright_grib.decoder_version()))
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _thread_count(pid: int) -> int | None:
|
|
233
|
+
"""How many threads the child has right now, read from outside it.
|
|
234
|
+
|
|
235
|
+
From outside, because from inside there is no way to look: the decode is one blocking call
|
|
236
|
+
into compiled code, a Python signal handler would not run until it returned, and a watcher
|
|
237
|
+
thread is exactly the thing the filter denies. The parent is not confined, so it can watch."""
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
with open(f"/proc/{pid}/status", encoding="ascii") as handle:
|
|
241
|
+
for line in handle:
|
|
242
|
+
if line.startswith("Threads:"):
|
|
243
|
+
return int(line.split()[1])
|
|
244
|
+
except (FileNotFoundError, ProcessLookupError):
|
|
245
|
+
return None
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _exercise_decoder_under_the_filter() -> None:
|
|
250
|
+
"""Import the decoder and decode every admitted sample inside the confined child.
|
|
251
|
+
|
|
252
|
+
Succeeding at all is the strongest half of the claim, because the filter denies thread
|
|
253
|
+
creation: a decoder with a worker pool does not reach the second line. The sampling below is
|
|
254
|
+
the explicit half, so the count is asserted rather than inferred from a survival."""
|
|
255
|
+
|
|
256
|
+
probe = subprocess.Popen(
|
|
257
|
+
[sys.executable, "-c", _decoder_probe()],
|
|
258
|
+
stdin=subprocess.DEVNULL,
|
|
259
|
+
stdout=subprocess.PIPE,
|
|
260
|
+
stderr=subprocess.PIPE,
|
|
261
|
+
env={},
|
|
262
|
+
close_fds=True,
|
|
263
|
+
preexec_fn=_install_networkless_seccomp,
|
|
264
|
+
)
|
|
265
|
+
assert probe.stdout is not None
|
|
266
|
+
if probe.stdout.readline() != b"decoder-ready\n":
|
|
267
|
+
probe.kill()
|
|
268
|
+
raise RuntimeError("weather decoder did not start under the crawler seccomp filter")
|
|
269
|
+
|
|
270
|
+
counts: list[int] = []
|
|
271
|
+
while probe.poll() is None:
|
|
272
|
+
observed = _thread_count(probe.pid)
|
|
273
|
+
if observed is None:
|
|
274
|
+
break
|
|
275
|
+
counts.append(observed)
|
|
276
|
+
time.sleep(_DECODER_SAMPLE_INTERVAL)
|
|
277
|
+
|
|
278
|
+
stdout, stderr = probe.communicate(timeout=30.0)
|
|
279
|
+
if probe.returncode != 0 or stderr:
|
|
280
|
+
raise RuntimeError(
|
|
281
|
+
f"weather decoder failed under the crawler seccomp filter with status "
|
|
282
|
+
f"{probe.returncode}"
|
|
283
|
+
)
|
|
284
|
+
if not stdout.startswith(b"decoder-ok "):
|
|
285
|
+
raise RuntimeError("weather decoder did not complete every admitted sample")
|
|
286
|
+
if len(counts) < _DECODER_LEAST_SAMPLES:
|
|
287
|
+
raise RuntimeError(
|
|
288
|
+
f"only {len(counts)} thread counts were taken while the decoder ran, which is too "
|
|
289
|
+
f"few to say anything about what it did during a decode"
|
|
290
|
+
)
|
|
291
|
+
if set(counts) != {1}:
|
|
292
|
+
raise RuntimeError(
|
|
293
|
+
f"the weather decoder ran on more than one thread during a decode: observed "
|
|
294
|
+
f"{sorted(set(counts))}"
|
|
295
|
+
)
|
|
296
|
+
print(stdout.decode("ascii").strip())
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def main() -> int:
|
|
300
|
+
"""Exercise normalization plus a malicious network probe under the production launcher."""
|
|
301
|
+
|
|
302
|
+
_disable_process_dumpability()
|
|
303
|
+
request = HostedCrawlerRequest(
|
|
304
|
+
request_id="12121212-1212-4212-8212-121212121212",
|
|
305
|
+
source_id="openligadb",
|
|
306
|
+
adapter_id="external.openligadb",
|
|
307
|
+
adapter_version="1.0.0",
|
|
308
|
+
requested_at="2026-07-20T12:00:00Z",
|
|
309
|
+
query={"league_shortcut": "bl1", "league_season": 2025},
|
|
310
|
+
egress_policy_attestation=OPENLIGADB_EGRESS_POLICY_ATTESTATION,
|
|
311
|
+
)
|
|
312
|
+
source_url = "https://api.openligadb.de/getmatchdata/bl1/2025"
|
|
313
|
+
normalization_input = encode_normalization_input(
|
|
314
|
+
request,
|
|
315
|
+
SimpleNamespace(
|
|
316
|
+
source_url=source_url,
|
|
317
|
+
final_url=source_url,
|
|
318
|
+
media_type="application/json",
|
|
319
|
+
content=_RAW_FIXTURE,
|
|
320
|
+
content_sha256=sha256_bytes(_RAW_FIXTURE),
|
|
321
|
+
transport_evidence_digest="f" * 64,
|
|
322
|
+
hops=(SimpleNamespace(status=200, etag=None, last_modified=None),),
|
|
323
|
+
),
|
|
324
|
+
)
|
|
325
|
+
result = parse_crawler_result(
|
|
326
|
+
parse_canonical_json(_run_crawler_child(request, normalization_input))
|
|
327
|
+
)
|
|
328
|
+
if result.request_digest != request.digest or b"FC Bayern Muenchen" not in (
|
|
329
|
+
result.normalized_content
|
|
330
|
+
):
|
|
331
|
+
raise RuntimeError("sandboxed normalizer result binding failed")
|
|
332
|
+
|
|
333
|
+
probe = subprocess.run(
|
|
334
|
+
[sys.executable, "-c", _MALICIOUS_PROBE],
|
|
335
|
+
input=b"",
|
|
336
|
+
capture_output=True,
|
|
337
|
+
env={},
|
|
338
|
+
close_fds=True,
|
|
339
|
+
preexec_fn=_install_networkless_seccomp,
|
|
340
|
+
check=False,
|
|
341
|
+
timeout=10.0,
|
|
342
|
+
)
|
|
343
|
+
if probe.returncode != 0 or probe.stdout != b"networkless-seccomp-ok\n" or probe.stderr:
|
|
344
|
+
raise RuntimeError("crawler seccomp malicious-network probe failed")
|
|
345
|
+
print("networkless-seccomp-ok")
|
|
346
|
+
_exercise_decoder_under_the_filter()
|
|
347
|
+
return 0
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
if __name__ == "__main__":
|
|
351
|
+
raise SystemExit(main())
|