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,486 @@
|
|
|
1
|
+
"""Deterministic nbformat materialization from reduced visual-run state."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import secrets
|
|
9
|
+
import stat
|
|
10
|
+
import sys
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from ctypes import CDLL, c_char_p, c_int, get_errno
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from mostlyright.data_harness.canonical import canonical_json_bytes, sha256_bytes
|
|
17
|
+
from mostlyright.data_harness.visual_run.contracts import (
|
|
18
|
+
DOCUMENT_HEADER_CELL_ID,
|
|
19
|
+
LEGACY_DOCUMENT_HEADER_CELL_ID_PREFIX,
|
|
20
|
+
)
|
|
21
|
+
from mostlyright.data_harness.visual_run.observations import scrub_for_display
|
|
22
|
+
from mostlyright.data_harness.visual_run.reducer import CellState, ReducedRun
|
|
23
|
+
|
|
24
|
+
NOTEBOOK_FORMAT = 4
|
|
25
|
+
NOTEBOOK_MINOR = 5
|
|
26
|
+
MAX_MATERIALIZED_NOTEBOOK_BYTES = 8 * 1024 * 1024
|
|
27
|
+
DOCUMENT_HEADER_SOURCE = (
|
|
28
|
+
"# Research record\n\n"
|
|
29
|
+
"This document explains the source choice, dataset rules, build steps, and checks.\n"
|
|
30
|
+
)
|
|
31
|
+
_MARKDOWN_FENCE = re.compile(r"^ {0,3}(`{3,}|~{3,})\s*([^`]*)$")
|
|
32
|
+
_MARKDOWN_H1 = re.compile(r"^#\s+")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _demote_legacy_h1(source: str) -> str:
|
|
36
|
+
"""Keep historical artifact truth while projecting agent H1 headings as H2 sections."""
|
|
37
|
+
|
|
38
|
+
lines = source.splitlines(keepends=True)
|
|
39
|
+
fence_character: str | None = None
|
|
40
|
+
projected: list[str] = []
|
|
41
|
+
for line in lines:
|
|
42
|
+
body = line.rstrip("\r\n")
|
|
43
|
+
if fence_character is not None:
|
|
44
|
+
closing = re.fullmatch(rf" {{0,3}}{re.escape(fence_character)}{{3,}}\s*", body)
|
|
45
|
+
if closing:
|
|
46
|
+
fence_character = None
|
|
47
|
+
projected.append(line)
|
|
48
|
+
continue
|
|
49
|
+
fence = _MARKDOWN_FENCE.match(body)
|
|
50
|
+
if fence:
|
|
51
|
+
fence_character = fence.group(1)[0]
|
|
52
|
+
projected.append(line)
|
|
53
|
+
continue
|
|
54
|
+
projected.append("#" + line if _MARKDOWN_H1.match(body) else line)
|
|
55
|
+
return "".join(projected)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _document_header_cell() -> dict[str, Any]:
|
|
59
|
+
"""Return the fixed opening cell shared by every research notebook projection."""
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
"cell_type": "markdown",
|
|
63
|
+
"id": DOCUMENT_HEADER_CELL_ID,
|
|
64
|
+
"metadata": {"mostlyright": {"document_header": True}},
|
|
65
|
+
"source": DOCUMENT_HEADER_SOURCE.splitlines(keepends=True),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _artifact_output_note(stage: str) -> str:
|
|
70
|
+
"""Describe a stored output without exposing its internal artifact identifier."""
|
|
71
|
+
|
|
72
|
+
label = " ".join(stage.replace("_", "-").split("-")).strip() or "step"
|
|
73
|
+
return f"{label.capitalize()} output is stored with this run.\n"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _cell(cell: CellState, artifact_reader: Callable[[Any], bytes] | None) -> dict[str, Any]:
|
|
77
|
+
mostlyright: dict[str, Any] = {
|
|
78
|
+
"object_version": cell.version,
|
|
79
|
+
"phase": cell.phase,
|
|
80
|
+
"stage": cell.stage,
|
|
81
|
+
"status": cell.status,
|
|
82
|
+
}
|
|
83
|
+
if cell.status in {"writing", "executing"}:
|
|
84
|
+
mostlyright["activity"] = {
|
|
85
|
+
"schema_version": "mostlyright.research-cell-activity.v1",
|
|
86
|
+
"state": cell.status,
|
|
87
|
+
}
|
|
88
|
+
metadata = {"mostlyright": mostlyright}
|
|
89
|
+
source = []
|
|
90
|
+
if cell.source is not None and artifact_reader is not None:
|
|
91
|
+
source = artifact_reader(cell.source).decode("utf-8").splitlines(keepends=True)
|
|
92
|
+
if cell.cell_type == "markdown":
|
|
93
|
+
source = _demote_legacy_h1("".join(source)).splitlines(keepends=True)
|
|
94
|
+
return {
|
|
95
|
+
"cell_type": "markdown",
|
|
96
|
+
"id": cell.cell_id,
|
|
97
|
+
"metadata": metadata,
|
|
98
|
+
"source": source,
|
|
99
|
+
}
|
|
100
|
+
outputs = []
|
|
101
|
+
for reference in cell.outputs:
|
|
102
|
+
output: Any = None
|
|
103
|
+
if artifact_reader is not None:
|
|
104
|
+
try:
|
|
105
|
+
output = json.loads(artifact_reader(reference))
|
|
106
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
107
|
+
output = None
|
|
108
|
+
if not isinstance(output, dict) or output.get("output_type") not in {
|
|
109
|
+
"stream",
|
|
110
|
+
"display_data",
|
|
111
|
+
"execute_result",
|
|
112
|
+
"error",
|
|
113
|
+
}:
|
|
114
|
+
output = {
|
|
115
|
+
"data": {
|
|
116
|
+
"application/vnd.mostlyright.artifact-reference.v1+json": (
|
|
117
|
+
reference.to_mapping()
|
|
118
|
+
),
|
|
119
|
+
"text/plain": [_artifact_output_note(cell.stage)],
|
|
120
|
+
},
|
|
121
|
+
"metadata": {},
|
|
122
|
+
"output_type": "display_data",
|
|
123
|
+
}
|
|
124
|
+
outputs.append(output)
|
|
125
|
+
return {
|
|
126
|
+
"cell_type": "code",
|
|
127
|
+
# Jupyter's live prompt is represented by a null execution count plus the exact
|
|
128
|
+
# persisted ``executing`` activity. The requested count becomes visible only when
|
|
129
|
+
# the cell completes, so a running cell renders as ``In [*]`` rather than falsely
|
|
130
|
+
# claiming the execution already finished.
|
|
131
|
+
"execution_count": None if cell.status == "executing" else cell.execution_count,
|
|
132
|
+
"id": cell.cell_id,
|
|
133
|
+
"metadata": metadata,
|
|
134
|
+
"outputs": outputs,
|
|
135
|
+
"source": source,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _observation_cell(
|
|
140
|
+
index: int,
|
|
141
|
+
reference: Any,
|
|
142
|
+
artifact_reader: Callable[[Any], bytes] | None,
|
|
143
|
+
) -> dict[str, Any]:
|
|
144
|
+
value: Any = None
|
|
145
|
+
if artifact_reader is not None:
|
|
146
|
+
try:
|
|
147
|
+
value = json.loads(artifact_reader(reference))
|
|
148
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
149
|
+
value = None
|
|
150
|
+
if isinstance(value, dict):
|
|
151
|
+
value, redacted = scrub_for_display(value)
|
|
152
|
+
assert isinstance(value, dict)
|
|
153
|
+
source_observation = (
|
|
154
|
+
reference.artifact_schema == "mostlyright.source-observation.v1"
|
|
155
|
+
and value.get("schema_version") == "mostlyright.source-observation.v1"
|
|
156
|
+
)
|
|
157
|
+
display = value.get("display", reference.display)
|
|
158
|
+
if not source_observation and display in {
|
|
159
|
+
"aggregate_only",
|
|
160
|
+
"schema_only",
|
|
161
|
+
"summary",
|
|
162
|
+
"redacted",
|
|
163
|
+
}:
|
|
164
|
+
value["sample"] = []
|
|
165
|
+
value["operation_parameters"] = {}
|
|
166
|
+
value["source_coordinates"] = []
|
|
167
|
+
coverage = value.get("coverage")
|
|
168
|
+
if isinstance(coverage, dict):
|
|
169
|
+
coverage["representative_window"] = None
|
|
170
|
+
if redacted and not source_observation:
|
|
171
|
+
value["display"] = "schema_only"
|
|
172
|
+
value["sample"] = []
|
|
173
|
+
value["operation_parameters"] = {}
|
|
174
|
+
value["source_coordinates"] = []
|
|
175
|
+
coverage = value.get("coverage")
|
|
176
|
+
if isinstance(coverage, dict):
|
|
177
|
+
coverage["representative_window"] = None
|
|
178
|
+
mime = (
|
|
179
|
+
"application/vnd.mostlyright.source-observation+json"
|
|
180
|
+
if source_observation
|
|
181
|
+
else "application/vnd.mostlyright.stage-observation+json"
|
|
182
|
+
)
|
|
183
|
+
label = "source" if source_observation else value.get("operation", "stage")
|
|
184
|
+
rows = value.get("row_count") if source_observation else value.get("output_rows")
|
|
185
|
+
data = {
|
|
186
|
+
mime: value,
|
|
187
|
+
"text/plain": [
|
|
188
|
+
f"Mostly Right {label} observation "
|
|
189
|
+
f"({rows if rows is not None else 'unknown'} rows)\n"
|
|
190
|
+
],
|
|
191
|
+
}
|
|
192
|
+
else:
|
|
193
|
+
data = {
|
|
194
|
+
"application/vnd.mostlyright.artifact-reference.v1+json": {
|
|
195
|
+
"sha256": reference.sha256,
|
|
196
|
+
"display": reference.display,
|
|
197
|
+
},
|
|
198
|
+
"text/plain": ["Mostly Right observation artifact\n"],
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
"cell_type": "code",
|
|
202
|
+
"execution_count": None,
|
|
203
|
+
"id": f"mr-observation-{index:04d}-{reference.sha256[:24]}",
|
|
204
|
+
"metadata": {
|
|
205
|
+
"mostlyright": {
|
|
206
|
+
"artifact_sha256": reference.sha256,
|
|
207
|
+
"passive": True,
|
|
208
|
+
"schema_version": "mostlyright.passive-observation-cell.v1",
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"outputs": [{"data": data, "metadata": {}, "output_type": "display_data"}],
|
|
212
|
+
"source": [],
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def notebook_value(
|
|
217
|
+
state: ReducedRun, artifact_reader: Callable[[Any], bytes] | None = None
|
|
218
|
+
) -> dict[str, Any]:
|
|
219
|
+
if any(key != cell.cell_id for key, cell in state.cells.items()):
|
|
220
|
+
raise ValueError("visual-run state has a cell id that differs from its mapping key")
|
|
221
|
+
# Refuse before calling an artifact reader: references are durable, authenticated size facts,
|
|
222
|
+
# so they provide the only way to prevent a huge source/output from being allocated first and
|
|
223
|
+
# rejected only after serialization.
|
|
224
|
+
artifact_bytes = sum(
|
|
225
|
+
reference.byte_size
|
|
226
|
+
for cell in state.cells.values()
|
|
227
|
+
for reference in ((cell.source,) if cell.source is not None else ()) + cell.outputs
|
|
228
|
+
) + sum(reference.byte_size for reference in state.observations)
|
|
229
|
+
if artifact_bytes > MAX_MATERIALIZED_NOTEBOOK_BYTES:
|
|
230
|
+
raise ValueError("visual-run artifacts exceed the materialized notebook byte limit")
|
|
231
|
+
authored_ids = {cell.cell_id for cell in state.cells.values()}
|
|
232
|
+
legacy_header_id = LEGACY_DOCUMENT_HEADER_CELL_ID_PREFIX
|
|
233
|
+
suffix = 2
|
|
234
|
+
while legacy_header_id in authored_ids:
|
|
235
|
+
legacy_header_id = f"{LEGACY_DOCUMENT_HEADER_CELL_ID_PREFIX}-{suffix}"
|
|
236
|
+
suffix += 1
|
|
237
|
+
cells = [_document_header_cell()]
|
|
238
|
+
for cell in state.cells.values():
|
|
239
|
+
projected = _cell(cell, artifact_reader)
|
|
240
|
+
if projected["id"] == DOCUMENT_HEADER_CELL_ID:
|
|
241
|
+
projected["id"] = legacy_header_id
|
|
242
|
+
cells.append(projected)
|
|
243
|
+
cells.extend(
|
|
244
|
+
_observation_cell(index, reference, artifact_reader)
|
|
245
|
+
for index, reference in enumerate(state.observations, 1)
|
|
246
|
+
)
|
|
247
|
+
return {
|
|
248
|
+
"cells": cells,
|
|
249
|
+
"metadata": {
|
|
250
|
+
"kernelspec": {
|
|
251
|
+
"display_name": "Python 3",
|
|
252
|
+
"language": "python",
|
|
253
|
+
"name": "python3",
|
|
254
|
+
},
|
|
255
|
+
"language_info": {"name": "python", "version": "3.13"},
|
|
256
|
+
"mostlyright": {
|
|
257
|
+
"cursor": state.cursor,
|
|
258
|
+
"lifecycle": state.lifecycle,
|
|
259
|
+
"run_id": state.run_id,
|
|
260
|
+
"schema_version": "mostlyright.research-notebook.v2",
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
"nbformat": NOTEBOOK_FORMAT,
|
|
264
|
+
"nbformat_minor": NOTEBOOK_MINOR,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def notebook_bytes(
|
|
269
|
+
state: ReducedRun, artifact_reader: Callable[[Any], bytes] | None = None
|
|
270
|
+
) -> bytes:
|
|
271
|
+
content = canonical_json_bytes(notebook_value(state, artifact_reader))
|
|
272
|
+
if len(content) > MAX_MATERIALIZED_NOTEBOOK_BYTES:
|
|
273
|
+
raise ValueError("generated notebook exceeds the visual materializer byte limit")
|
|
274
|
+
return content
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def notebook_digest(
|
|
278
|
+
state: ReducedRun, artifact_reader: Callable[[Any], bytes] | None = None
|
|
279
|
+
) -> str:
|
|
280
|
+
return sha256_bytes(notebook_bytes(state, artifact_reader))
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def materialize_notebook(
|
|
284
|
+
path: Path,
|
|
285
|
+
state: ReducedRun,
|
|
286
|
+
artifact_reader: Callable[[Any], bytes] | None = None,
|
|
287
|
+
*,
|
|
288
|
+
directory_fd: int | None = None,
|
|
289
|
+
) -> str:
|
|
290
|
+
path = Path(path)
|
|
291
|
+
content = notebook_bytes(state, artifact_reader)
|
|
292
|
+
owned_directory = directory_fd is None
|
|
293
|
+
if directory_fd is None:
|
|
294
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
295
|
+
directory_fd = os.open(
|
|
296
|
+
path.parent,
|
|
297
|
+
os.O_RDONLY | os.O_DIRECTORY | getattr(os, "O_NOFOLLOW", 0),
|
|
298
|
+
)
|
|
299
|
+
try:
|
|
300
|
+
existing_raw, existing_identity = _read_existing(directory_fd, path.name)
|
|
301
|
+
if existing_raw is not None:
|
|
302
|
+
try:
|
|
303
|
+
existing = json.loads(existing_raw)
|
|
304
|
+
except (UnicodeError, json.JSONDecodeError) as exc:
|
|
305
|
+
raise ValueError("existing notebook is not a valid derived notebook") from exc
|
|
306
|
+
mostlyright = existing.get("metadata", {}).get("mostlyright", {})
|
|
307
|
+
marker = mostlyright.get("schema_version")
|
|
308
|
+
seeded = mostlyright.get("phase") == "ready_for_research" and all(
|
|
309
|
+
cell.get("metadata", {}).get("mostlyright", {}).get("seed") is True
|
|
310
|
+
for cell in existing.get("cells", ())
|
|
311
|
+
)
|
|
312
|
+
if marker != "mostlyright.research-notebook.v2" and not seeded:
|
|
313
|
+
raise ValueError(
|
|
314
|
+
"refusing to replace a notebook not owned by the visual materializer"
|
|
315
|
+
)
|
|
316
|
+
if existing_raw == content:
|
|
317
|
+
return sha256_bytes(content)
|
|
318
|
+
temporary = f".{path.name}.{secrets.token_hex(12)}.tmp"
|
|
319
|
+
descriptor = os.open(
|
|
320
|
+
temporary,
|
|
321
|
+
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_CLOEXEC | os.O_NOFOLLOW,
|
|
322
|
+
0o600,
|
|
323
|
+
dir_fd=directory_fd,
|
|
324
|
+
)
|
|
325
|
+
try:
|
|
326
|
+
written = 0
|
|
327
|
+
while written < len(content):
|
|
328
|
+
progress = os.write(descriptor, content[written:])
|
|
329
|
+
if progress <= 0:
|
|
330
|
+
raise OSError("notebook write made no progress")
|
|
331
|
+
written += progress
|
|
332
|
+
os.fsync(descriptor)
|
|
333
|
+
finally:
|
|
334
|
+
os.close(descriptor)
|
|
335
|
+
_install_if_unchanged(
|
|
336
|
+
directory_fd,
|
|
337
|
+
temporary,
|
|
338
|
+
path.name,
|
|
339
|
+
existing_identity,
|
|
340
|
+
sha256_bytes(existing_raw) if existing_raw is not None else None,
|
|
341
|
+
)
|
|
342
|
+
os.fsync(directory_fd)
|
|
343
|
+
return sha256_bytes(content)
|
|
344
|
+
finally:
|
|
345
|
+
if owned_directory:
|
|
346
|
+
os.close(directory_fd)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _file_identity(observed: os.stat_result) -> tuple[int, int, int, int, int, int, int]:
|
|
350
|
+
return (
|
|
351
|
+
observed.st_dev,
|
|
352
|
+
observed.st_ino,
|
|
353
|
+
observed.st_mode,
|
|
354
|
+
observed.st_nlink,
|
|
355
|
+
observed.st_size,
|
|
356
|
+
observed.st_mtime_ns,
|
|
357
|
+
observed.st_ctime_ns,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _read_existing(
|
|
362
|
+
directory_fd: int, name: str
|
|
363
|
+
) -> tuple[bytes | None, tuple[int, int, int, int, int, int, int] | None]:
|
|
364
|
+
try:
|
|
365
|
+
descriptor = os.open(
|
|
366
|
+
name,
|
|
367
|
+
os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW,
|
|
368
|
+
dir_fd=directory_fd,
|
|
369
|
+
)
|
|
370
|
+
except FileNotFoundError:
|
|
371
|
+
return None, None
|
|
372
|
+
try:
|
|
373
|
+
observed = os.fstat(descriptor)
|
|
374
|
+
if not stat.S_ISREG(observed.st_mode) or observed.st_nlink != 1:
|
|
375
|
+
raise ValueError("existing notebook must be a regular single-link file")
|
|
376
|
+
if observed.st_size > MAX_MATERIALIZED_NOTEBOOK_BYTES:
|
|
377
|
+
raise ValueError("existing notebook exceeds the visual materializer byte limit")
|
|
378
|
+
chunks: list[bytes] = []
|
|
379
|
+
remaining = MAX_MATERIALIZED_NOTEBOOK_BYTES + 1
|
|
380
|
+
while remaining and (chunk := os.read(descriptor, min(64 * 1024, remaining))):
|
|
381
|
+
chunks.append(chunk)
|
|
382
|
+
remaining -= len(chunk)
|
|
383
|
+
raw = b"".join(chunks)
|
|
384
|
+
after = os.fstat(descriptor)
|
|
385
|
+
if len(raw) > MAX_MATERIALIZED_NOTEBOOK_BYTES or _file_identity(after) != _file_identity(
|
|
386
|
+
observed
|
|
387
|
+
):
|
|
388
|
+
raise ValueError("existing notebook changed while it was being read")
|
|
389
|
+
return raw, _file_identity(observed)
|
|
390
|
+
finally:
|
|
391
|
+
os.close(descriptor)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _require_destination_identity(
|
|
395
|
+
directory_fd: int,
|
|
396
|
+
name: str,
|
|
397
|
+
expected: tuple[int, int, int, int, int, int, int] | None,
|
|
398
|
+
expected_sha256: str | None,
|
|
399
|
+
) -> None:
|
|
400
|
+
observed_raw, observed_identity = _read_existing(directory_fd, name)
|
|
401
|
+
# Linux updates ctime during RENAME_EXCHANGE, so it cannot be compared after the atomic swap.
|
|
402
|
+
# Device/inode/mode/link identity plus the exact bounded content digest proves that the
|
|
403
|
+
# displaced object is the one we admitted, even if an attacker tried to spoof timestamps.
|
|
404
|
+
if (
|
|
405
|
+
observed_identity is None
|
|
406
|
+
or expected is None
|
|
407
|
+
or observed_identity[:4] != expected[:4]
|
|
408
|
+
or observed_raw is None
|
|
409
|
+
or expected_sha256 is None
|
|
410
|
+
or sha256_bytes(observed_raw) != expected_sha256
|
|
411
|
+
):
|
|
412
|
+
raise ValueError("existing notebook changed before atomic materialization")
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _rename_with_flags(directory_fd: int, source: str, destination: str, flag: int) -> None:
|
|
416
|
+
libc = CDLL(None, use_errno=True)
|
|
417
|
+
encoded_source = os.fsencode(source)
|
|
418
|
+
encoded_destination = os.fsencode(destination)
|
|
419
|
+
if sys.platform == "darwin":
|
|
420
|
+
result = libc.renameatx_np(
|
|
421
|
+
c_int(directory_fd),
|
|
422
|
+
c_char_p(encoded_source),
|
|
423
|
+
c_int(directory_fd),
|
|
424
|
+
c_char_p(encoded_destination),
|
|
425
|
+
c_int(flag),
|
|
426
|
+
)
|
|
427
|
+
else:
|
|
428
|
+
result = libc.renameat2(
|
|
429
|
+
c_int(directory_fd),
|
|
430
|
+
c_char_p(encoded_source),
|
|
431
|
+
c_int(directory_fd),
|
|
432
|
+
c_char_p(encoded_destination),
|
|
433
|
+
c_int(flag),
|
|
434
|
+
)
|
|
435
|
+
if result != 0:
|
|
436
|
+
error = get_errno()
|
|
437
|
+
raise OSError(error, os.strerror(error), destination)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _install_if_unchanged(
|
|
441
|
+
directory_fd: int,
|
|
442
|
+
temporary: str,
|
|
443
|
+
destination: str,
|
|
444
|
+
expected: tuple[int, int, int, int, int, int, int] | None,
|
|
445
|
+
expected_sha256: str | None,
|
|
446
|
+
) -> None:
|
|
447
|
+
"""Atomically install without ever overwriting an unchecked destination object."""
|
|
448
|
+
|
|
449
|
+
if expected is None:
|
|
450
|
+
# Darwin RENAME_EXCL and Linux RENAME_NOREPLACE have different flag values.
|
|
451
|
+
try:
|
|
452
|
+
_rename_with_flags(
|
|
453
|
+
directory_fd,
|
|
454
|
+
temporary,
|
|
455
|
+
destination,
|
|
456
|
+
0x00000004 if sys.platform == "darwin" else 1,
|
|
457
|
+
)
|
|
458
|
+
except BaseException:
|
|
459
|
+
# A failed no-replace leaves our generated file at the temporary name.
|
|
460
|
+
os.unlink(temporary, dir_fd=directory_fd)
|
|
461
|
+
raise
|
|
462
|
+
return
|
|
463
|
+
# Exchange is the only portable kernel primitive that prevents a substitution between a
|
|
464
|
+
# userspace identity check and installation. The displaced object remains recoverable under
|
|
465
|
+
# our random temporary name until its identity is proven.
|
|
466
|
+
try:
|
|
467
|
+
_rename_with_flags(directory_fd, temporary, destination, 0x00000002)
|
|
468
|
+
except BaseException:
|
|
469
|
+
# A failed exchange leaves our generated file at the temporary name.
|
|
470
|
+
os.unlink(temporary, dir_fd=directory_fd)
|
|
471
|
+
raise
|
|
472
|
+
try:
|
|
473
|
+
_require_destination_identity(directory_fd, temporary, expected, expected_sha256)
|
|
474
|
+
except BaseException as identity_error:
|
|
475
|
+
# Restore both names atomically; never unlink the displaced, unchecked object.
|
|
476
|
+
try:
|
|
477
|
+
_rename_with_flags(directory_fd, temporary, destination, 0x00000002)
|
|
478
|
+
except BaseException as rollback_error:
|
|
479
|
+
# Both objects remain recoverable. In particular, the unchecked displaced object is
|
|
480
|
+
# never deleted merely because restoration encountered an operating-system failure.
|
|
481
|
+
raise RuntimeError(
|
|
482
|
+
"atomic notebook replacement could not be restored"
|
|
483
|
+
) from rollback_error
|
|
484
|
+
os.unlink(temporary, dir_fd=directory_fd)
|
|
485
|
+
raise identity_error
|
|
486
|
+
os.unlink(temporary, dir_fd=directory_fd)
|