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,892 @@
|
|
|
1
|
+
"""The supported agent-facing API for durable, observable research work."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from collections.abc import Callable, Mapping
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mostlyright.data_harness.canonical import canonical_json_bytes
|
|
11
|
+
from mostlyright.data_harness.visual_run.contracts import (
|
|
12
|
+
ACTORS,
|
|
13
|
+
ArtifactReference,
|
|
14
|
+
TerminalError,
|
|
15
|
+
VisualRunDraft,
|
|
16
|
+
VisualRunError,
|
|
17
|
+
VisualRunEvent,
|
|
18
|
+
)
|
|
19
|
+
from mostlyright.data_harness.visual_run.materialize import MAX_MATERIALIZED_NOTEBOOK_BYTES
|
|
20
|
+
from mostlyright.data_harness.visual_run.observations import (
|
|
21
|
+
scrub_for_display,
|
|
22
|
+
validate_stage_observation,
|
|
23
|
+
)
|
|
24
|
+
from mostlyright.data_harness.visual_run.reducer import CellState, reduce_events
|
|
25
|
+
from mostlyright.data_harness.visual_run.store import VisualRunStore
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def validate_markdown_checkpoint(source: str) -> None:
|
|
29
|
+
"""Load renderer-backed authoring policy only when an agent writes markdown."""
|
|
30
|
+
|
|
31
|
+
from mostlyright.data_harness.visual_run.authoring import validate_markdown_checkpoint
|
|
32
|
+
|
|
33
|
+
validate_markdown_checkpoint(source)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def validate_markdown_completion(source: str) -> None:
|
|
37
|
+
"""Load renderer-backed completion policy only when an agent completes markdown."""
|
|
38
|
+
|
|
39
|
+
from mostlyright.data_harness.visual_run.authoring import validate_markdown_completion
|
|
40
|
+
|
|
41
|
+
validate_markdown_completion(source)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def validate_markdown_document(source: str) -> None:
|
|
45
|
+
"""Load the full-document authoring rule only for agent markdown writes."""
|
|
46
|
+
|
|
47
|
+
from mostlyright.data_harness.visual_run.authoring import validate_markdown_document
|
|
48
|
+
|
|
49
|
+
validate_markdown_document(source)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _utc_now() -> str:
|
|
53
|
+
return datetime.now(UTC).isoformat(timespec="microseconds").replace("+00:00", "Z")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def next_attempt_coordinate(store: VisualRunStore, scope: str, attempt: str) -> str:
|
|
57
|
+
"""Return the active or next durable attempt coordinate for a retryable command.
|
|
58
|
+
|
|
59
|
+
Producer attempt ids are fences and intentionally remain stable across process retries. A
|
|
60
|
+
visual phase, however, is one concrete invocation. Reusing the fence as the phase identity
|
|
61
|
+
caused a later failure to collide with the first invocation's terminal event and mask the
|
|
62
|
+
real error. Active phases are resumed idempotently; completed/failed phases advance to a
|
|
63
|
+
deterministic retry coordinate derived only from the durable log.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
if not scope or ":" in scope:
|
|
67
|
+
raise ValueError("attempt scope must be a non-empty coordinate segment")
|
|
68
|
+
state = reduce_events(store.events())
|
|
69
|
+
ordinal = 0
|
|
70
|
+
while True:
|
|
71
|
+
base = f"{scope}:{attempt}"
|
|
72
|
+
coordinate = base if ordinal == 0 else f"{base}:retry-{ordinal}"
|
|
73
|
+
object_id = VisualWork._attempt_identity(coordinate)
|
|
74
|
+
existing = state.objects.get(object_id)
|
|
75
|
+
if existing is None or existing.status == "active":
|
|
76
|
+
return coordinate
|
|
77
|
+
ordinal += 1
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def attempt_event_key(scope: str, coordinate: str, transition: str) -> str:
|
|
81
|
+
"""Bind an attempt transition key to its invocation without exposing long coordinates."""
|
|
82
|
+
|
|
83
|
+
if transition not in {"started", "completed", "failed"}:
|
|
84
|
+
raise ValueError("unknown attempt transition")
|
|
85
|
+
digest = hashlib.sha256(coordinate.encode("utf-8")).hexdigest()[:24]
|
|
86
|
+
return f"{scope}:attempt:{digest}:{transition}"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class VisualWork:
|
|
90
|
+
"""Persist meaningful agent cell phases through one versioned API.
|
|
91
|
+
|
|
92
|
+
Callers provide complete source/output states at real checkpoints. The SDK never sleeps,
|
|
93
|
+
slices text, invents progress, or derives ordering from timestamps.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def _run_identity(run_id: str) -> str:
|
|
98
|
+
return f"run:{hashlib.sha256(run_id.encode('utf-8')).hexdigest()[:24]}"
|
|
99
|
+
|
|
100
|
+
def _run_object_id(self) -> str:
|
|
101
|
+
"""Use the namespaced identity, while preserving already-durable v1 run objects."""
|
|
102
|
+
|
|
103
|
+
state = self._state()
|
|
104
|
+
existing = [object_id for object_id, kind in state.object_kinds.items() if kind == "run"]
|
|
105
|
+
if len(existing) > 1:
|
|
106
|
+
raise VisualRunError("VISUAL_RUN_TRANSITION", "run has multiple identity objects")
|
|
107
|
+
return existing[0] if existing else self._run_identity(self.store.run_id)
|
|
108
|
+
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
store: VisualRunStore,
|
|
112
|
+
*,
|
|
113
|
+
observed_at: Callable[[], str] = _utc_now,
|
|
114
|
+
actor: str = "agent",
|
|
115
|
+
) -> None:
|
|
116
|
+
if actor not in ACTORS:
|
|
117
|
+
raise ValueError("actor is outside the visual-run contract")
|
|
118
|
+
self.store = store
|
|
119
|
+
self._observed_at = observed_at
|
|
120
|
+
self._actor = actor
|
|
121
|
+
|
|
122
|
+
def _state(self) -> Any:
|
|
123
|
+
return reduce_events(self.store.events())
|
|
124
|
+
|
|
125
|
+
def _cell(self, cell_id: str) -> CellState:
|
|
126
|
+
cell = self._state().cells.get(cell_id)
|
|
127
|
+
if cell is None:
|
|
128
|
+
raise KeyError(cell_id)
|
|
129
|
+
return cell
|
|
130
|
+
|
|
131
|
+
def _append(
|
|
132
|
+
self,
|
|
133
|
+
event_type: str,
|
|
134
|
+
key: str,
|
|
135
|
+
*,
|
|
136
|
+
object_id: str,
|
|
137
|
+
object_version: int,
|
|
138
|
+
previous_object_version: int | None,
|
|
139
|
+
payload: Mapping[str, Any],
|
|
140
|
+
artifact: ArtifactReference | None = None,
|
|
141
|
+
status: str | None = None,
|
|
142
|
+
error: TerminalError | None = None,
|
|
143
|
+
phase: str = "researching",
|
|
144
|
+
stage: str = "authoring",
|
|
145
|
+
) -> VisualRunEvent:
|
|
146
|
+
existing = self.store.event_for_key(key)
|
|
147
|
+
draft = VisualRunDraft.from_mapping(
|
|
148
|
+
{
|
|
149
|
+
"schema_version": "visual-run-event.v2",
|
|
150
|
+
"run_id": self.store.run_id,
|
|
151
|
+
"event_type": event_type,
|
|
152
|
+
"event_version": 1,
|
|
153
|
+
"actor": self._actor,
|
|
154
|
+
"phase": phase,
|
|
155
|
+
"stage": stage,
|
|
156
|
+
"object_id": object_id,
|
|
157
|
+
"object_version": object_version,
|
|
158
|
+
"previous_object_version": previous_object_version,
|
|
159
|
+
# Retries compare the durable timestamp, because time is not command intent.
|
|
160
|
+
"observed_at": existing.observed_at
|
|
161
|
+
if existing is not None
|
|
162
|
+
else self._observed_at(),
|
|
163
|
+
"idempotency_key": key,
|
|
164
|
+
"payload": dict(payload),
|
|
165
|
+
"artifact": artifact.to_mapping() if artifact is not None else None,
|
|
166
|
+
"status": status,
|
|
167
|
+
"error": error.to_mapping() if error is not None else None,
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
if existing is not None:
|
|
171
|
+
existing_value = VisualRunDraft.from_event(existing).to_mapping()
|
|
172
|
+
retry_value = draft.to_mapping()
|
|
173
|
+
existing_value.pop("observed_at")
|
|
174
|
+
retry_value.pop("observed_at")
|
|
175
|
+
if existing_value == retry_value:
|
|
176
|
+
return existing
|
|
177
|
+
raise VisualRunError(
|
|
178
|
+
"VISUAL_RUN_IDEMPOTENCY_CONFLICT", "idempotency key names another command"
|
|
179
|
+
)
|
|
180
|
+
return self.store.append(draft)
|
|
181
|
+
|
|
182
|
+
def start_run(self, *, key: str = "run-started") -> VisualRunEvent:
|
|
183
|
+
return self._append(
|
|
184
|
+
"run.started",
|
|
185
|
+
key,
|
|
186
|
+
# Run identity and object identity are separate namespaces. Hashing keeps an
|
|
187
|
+
# operator-selected run id such as ``build`` from colliding with fixed worker
|
|
188
|
+
# objects while the event's top-level ``run_id`` remains the public identity.
|
|
189
|
+
object_id=self._run_object_id(),
|
|
190
|
+
object_version=1,
|
|
191
|
+
previous_object_version=None,
|
|
192
|
+
payload={},
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def start_cell(
|
|
196
|
+
self,
|
|
197
|
+
cell_id: str,
|
|
198
|
+
cell_type: str,
|
|
199
|
+
*,
|
|
200
|
+
key: str,
|
|
201
|
+
after_cell_id: str | None = None,
|
|
202
|
+
) -> VisualRunEvent:
|
|
203
|
+
created = self._append(
|
|
204
|
+
"cell.created",
|
|
205
|
+
key,
|
|
206
|
+
object_id=cell_id,
|
|
207
|
+
object_version=1,
|
|
208
|
+
previous_object_version=None,
|
|
209
|
+
payload={"cell_type": cell_type},
|
|
210
|
+
)
|
|
211
|
+
if after_cell_id is not None:
|
|
212
|
+
self.move_after(cell_id, after_cell_id, key=f"{key}-position")
|
|
213
|
+
return created
|
|
214
|
+
|
|
215
|
+
def update_source(self, cell_id: str, source: str, *, key: str) -> VisualRunEvent:
|
|
216
|
+
if not isinstance(source, str):
|
|
217
|
+
raise TypeError("source must be text")
|
|
218
|
+
content = source.encode("utf-8")
|
|
219
|
+
existing = self.store.event_for_key(key)
|
|
220
|
+
if existing is not None:
|
|
221
|
+
reference = self._retry_artifact_reference(
|
|
222
|
+
existing,
|
|
223
|
+
content,
|
|
224
|
+
expected_schema="mostlyright.cell-source.v1",
|
|
225
|
+
)
|
|
226
|
+
return self._append(
|
|
227
|
+
"cell.source_extended",
|
|
228
|
+
key,
|
|
229
|
+
object_id=cell_id,
|
|
230
|
+
object_version=existing.object_version,
|
|
231
|
+
previous_object_version=existing.previous_object_version,
|
|
232
|
+
payload={"source_length": len(source)},
|
|
233
|
+
artifact=reference,
|
|
234
|
+
)
|
|
235
|
+
cell = self._cell(cell_id)
|
|
236
|
+
if cell.source is not None:
|
|
237
|
+
previous = self.store.read_artifact(cell.source)
|
|
238
|
+
if not content.startswith(previous) or content == previous:
|
|
239
|
+
raise ValueError("source extension must strictly append to the prior source")
|
|
240
|
+
if self._actor == "agent" and cell.cell_type == "markdown":
|
|
241
|
+
validate_markdown_document(content.decode("utf-8"))
|
|
242
|
+
previous_length = 0 if cell.source is None else len(previous)
|
|
243
|
+
validate_markdown_checkpoint(content[previous_length:].decode("utf-8"))
|
|
244
|
+
media_type = "text/markdown" if cell.cell_type == "markdown" else "text/x-python"
|
|
245
|
+
reference = self._install(
|
|
246
|
+
content,
|
|
247
|
+
artifact_id=f"{cell_id}-source-v{cell.version + 1}",
|
|
248
|
+
media_type=media_type,
|
|
249
|
+
artifact_schema="mostlyright.cell-source.v1",
|
|
250
|
+
projected=True,
|
|
251
|
+
replacing=cell.source,
|
|
252
|
+
)
|
|
253
|
+
return self._append(
|
|
254
|
+
"cell.source_extended",
|
|
255
|
+
key,
|
|
256
|
+
object_id=cell_id,
|
|
257
|
+
object_version=cell.version + 1,
|
|
258
|
+
previous_object_version=cell.version,
|
|
259
|
+
payload={"source_length": len(source)},
|
|
260
|
+
artifact=reference,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def revise_source(self, cell_id: str, source: str, *, key: str) -> VisualRunEvent:
|
|
264
|
+
if not isinstance(source, str):
|
|
265
|
+
raise TypeError("source must be text")
|
|
266
|
+
content = source.encode("utf-8")
|
|
267
|
+
existing = self.store.event_for_key(key)
|
|
268
|
+
if existing is not None:
|
|
269
|
+
reference = self._retry_artifact_reference(
|
|
270
|
+
existing,
|
|
271
|
+
content,
|
|
272
|
+
expected_schema="mostlyright.cell-source.v1",
|
|
273
|
+
)
|
|
274
|
+
return self._append(
|
|
275
|
+
"cell.source_revised",
|
|
276
|
+
key,
|
|
277
|
+
object_id=cell_id,
|
|
278
|
+
object_version=existing.object_version,
|
|
279
|
+
previous_object_version=existing.previous_object_version,
|
|
280
|
+
payload={"source_length": len(source)},
|
|
281
|
+
artifact=reference,
|
|
282
|
+
)
|
|
283
|
+
cell = self._cell(cell_id)
|
|
284
|
+
if self._actor == "agent" and cell.cell_type == "markdown":
|
|
285
|
+
validate_markdown_document(source)
|
|
286
|
+
validate_markdown_checkpoint(source)
|
|
287
|
+
media_type = "text/markdown" if cell.cell_type == "markdown" else "text/x-python"
|
|
288
|
+
reference = self._install(
|
|
289
|
+
content,
|
|
290
|
+
artifact_id=f"{cell_id}-revision-v{cell.version + 1}",
|
|
291
|
+
media_type=media_type,
|
|
292
|
+
artifact_schema="mostlyright.cell-source.v1",
|
|
293
|
+
projected=True,
|
|
294
|
+
replacing=cell.source,
|
|
295
|
+
)
|
|
296
|
+
return self._append(
|
|
297
|
+
"cell.source_revised",
|
|
298
|
+
key,
|
|
299
|
+
object_id=cell_id,
|
|
300
|
+
object_version=cell.version + 1,
|
|
301
|
+
previous_object_version=cell.version,
|
|
302
|
+
payload={"source_length": len(source)},
|
|
303
|
+
artifact=reference,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
def move_after(self, cell_id: str, after_cell_id: str, *, key: str) -> VisualRunEvent:
|
|
307
|
+
existing = self.store.event_for_key(key)
|
|
308
|
+
if existing is not None:
|
|
309
|
+
return self._append(
|
|
310
|
+
"cell.moved",
|
|
311
|
+
key,
|
|
312
|
+
object_id=cell_id,
|
|
313
|
+
object_version=existing.object_version,
|
|
314
|
+
previous_object_version=existing.previous_object_version,
|
|
315
|
+
payload={"after_cell_id": after_cell_id},
|
|
316
|
+
)
|
|
317
|
+
cell = self._cell(cell_id)
|
|
318
|
+
return self._append(
|
|
319
|
+
"cell.moved",
|
|
320
|
+
key,
|
|
321
|
+
object_id=cell_id,
|
|
322
|
+
object_version=cell.version + 1,
|
|
323
|
+
previous_object_version=cell.version,
|
|
324
|
+
payload={"after_cell_id": after_cell_id},
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
def execution_started(self, cell_id: str, execution_count: int, *, key: str) -> VisualRunEvent:
|
|
328
|
+
existing = self.store.event_for_key(key)
|
|
329
|
+
if existing is not None:
|
|
330
|
+
return self._append(
|
|
331
|
+
"cell.execution_started",
|
|
332
|
+
key,
|
|
333
|
+
object_id=cell_id,
|
|
334
|
+
object_version=existing.object_version,
|
|
335
|
+
previous_object_version=existing.previous_object_version,
|
|
336
|
+
payload={"execution_count": execution_count},
|
|
337
|
+
stage="executing",
|
|
338
|
+
)
|
|
339
|
+
cell = self._cell(cell_id)
|
|
340
|
+
return self._append(
|
|
341
|
+
"cell.execution_started",
|
|
342
|
+
key,
|
|
343
|
+
object_id=cell_id,
|
|
344
|
+
object_version=cell.version + 1,
|
|
345
|
+
previous_object_version=cell.version,
|
|
346
|
+
payload={"execution_count": execution_count},
|
|
347
|
+
stage="executing",
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def output(
|
|
351
|
+
self,
|
|
352
|
+
cell_id: str,
|
|
353
|
+
output: Mapping[str, Any],
|
|
354
|
+
*,
|
|
355
|
+
output_kind: str,
|
|
356
|
+
key: str,
|
|
357
|
+
index: int | None = None,
|
|
358
|
+
) -> VisualRunEvent:
|
|
359
|
+
import json
|
|
360
|
+
|
|
361
|
+
content = (
|
|
362
|
+
json.dumps(
|
|
363
|
+
output, ensure_ascii=False, allow_nan=False, sort_keys=True, separators=(",", ":")
|
|
364
|
+
)
|
|
365
|
+
+ "\n"
|
|
366
|
+
).encode()
|
|
367
|
+
existing = self.store.event_for_key(key)
|
|
368
|
+
replace = index is not None
|
|
369
|
+
if existing is not None:
|
|
370
|
+
output_index = index if index is not None else existing.payload.get("output_index")
|
|
371
|
+
reference = self._retry_artifact_reference(
|
|
372
|
+
existing,
|
|
373
|
+
content,
|
|
374
|
+
expected_schema="mostlyright.cell-output.v1",
|
|
375
|
+
)
|
|
376
|
+
return self._append(
|
|
377
|
+
"cell.output_replaced" if replace else "cell.output_appended",
|
|
378
|
+
key,
|
|
379
|
+
object_id=cell_id,
|
|
380
|
+
object_version=existing.object_version,
|
|
381
|
+
previous_object_version=existing.previous_object_version,
|
|
382
|
+
payload={"output_index": output_index, "output_kind": output_kind},
|
|
383
|
+
artifact=reference,
|
|
384
|
+
stage="executing",
|
|
385
|
+
)
|
|
386
|
+
cell = self._cell(cell_id)
|
|
387
|
+
output_index = index if index is not None else len(cell.outputs)
|
|
388
|
+
if replace and not 0 <= output_index < len(cell.outputs):
|
|
389
|
+
raise ValueError("output replacement index is outside the current outputs")
|
|
390
|
+
reference = self._install(
|
|
391
|
+
content,
|
|
392
|
+
artifact_id=f"{cell_id}-output-{output_index}-v{cell.version + 1}",
|
|
393
|
+
media_type="application/json",
|
|
394
|
+
artifact_schema="mostlyright.cell-output.v1",
|
|
395
|
+
projected=True,
|
|
396
|
+
replacing=cell.outputs[output_index] if replace else None,
|
|
397
|
+
)
|
|
398
|
+
return self._append(
|
|
399
|
+
"cell.output_replaced" if replace else "cell.output_appended",
|
|
400
|
+
key,
|
|
401
|
+
object_id=cell_id,
|
|
402
|
+
object_version=cell.version + 1,
|
|
403
|
+
previous_object_version=cell.version,
|
|
404
|
+
payload={"output_index": output_index, "output_kind": output_kind},
|
|
405
|
+
artifact=reference,
|
|
406
|
+
stage="executing",
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def complete(self, cell_id: str, *, key: str) -> VisualRunEvent:
|
|
410
|
+
existing = self.store.event_for_key(key)
|
|
411
|
+
if existing is not None:
|
|
412
|
+
return self._append(
|
|
413
|
+
"cell.completed",
|
|
414
|
+
key,
|
|
415
|
+
object_id=cell_id,
|
|
416
|
+
object_version=existing.object_version,
|
|
417
|
+
previous_object_version=existing.previous_object_version,
|
|
418
|
+
payload={},
|
|
419
|
+
stage="complete",
|
|
420
|
+
)
|
|
421
|
+
cell = self._cell(cell_id)
|
|
422
|
+
if self._actor == "agent" and cell.cell_type == "markdown" and cell.source is not None:
|
|
423
|
+
validate_markdown_completion(self.store.read_artifact(cell.source).decode("utf-8"))
|
|
424
|
+
return self._append(
|
|
425
|
+
"cell.completed",
|
|
426
|
+
key,
|
|
427
|
+
object_id=cell_id,
|
|
428
|
+
object_version=cell.version + 1,
|
|
429
|
+
previous_object_version=cell.version,
|
|
430
|
+
payload={},
|
|
431
|
+
stage="complete",
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def fail(self, cell_id: str, error: TerminalError, *, key: str) -> VisualRunEvent:
|
|
435
|
+
existing = self.store.event_for_key(key)
|
|
436
|
+
if existing is not None:
|
|
437
|
+
return self._append(
|
|
438
|
+
"cell.failed",
|
|
439
|
+
key,
|
|
440
|
+
object_id=cell_id,
|
|
441
|
+
object_version=existing.object_version,
|
|
442
|
+
previous_object_version=existing.previous_object_version,
|
|
443
|
+
payload={},
|
|
444
|
+
error=error,
|
|
445
|
+
stage="failed",
|
|
446
|
+
)
|
|
447
|
+
cell = self._cell(cell_id)
|
|
448
|
+
return self._append(
|
|
449
|
+
"cell.failed",
|
|
450
|
+
key,
|
|
451
|
+
object_id=cell_id,
|
|
452
|
+
object_version=cell.version + 1,
|
|
453
|
+
previous_object_version=cell.version,
|
|
454
|
+
payload={},
|
|
455
|
+
error=error,
|
|
456
|
+
stage="failed",
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
def _update_versions(self, object_id: str, key: str) -> tuple[int, int]:
|
|
460
|
+
existing = self.store.event_for_key(key)
|
|
461
|
+
if existing is not None:
|
|
462
|
+
if existing.previous_object_version is None:
|
|
463
|
+
raise VisualRunError(
|
|
464
|
+
"VISUAL_RUN_IDEMPOTENCY_CONFLICT", "update key names a creation command"
|
|
465
|
+
)
|
|
466
|
+
return existing.object_version, existing.previous_object_version
|
|
467
|
+
current = self._state().object_versions.get(object_id)
|
|
468
|
+
if current is None:
|
|
469
|
+
raise KeyError(object_id)
|
|
470
|
+
return current + 1, current
|
|
471
|
+
|
|
472
|
+
@staticmethod
|
|
473
|
+
def _stage_identity(node_id: str) -> tuple[str, str, str]:
|
|
474
|
+
digest = hashlib.sha256(node_id.encode("utf-8")).hexdigest()
|
|
475
|
+
coordinate = node_id if len(node_id) <= 64 else f"{node_id[:47]}-{digest[:16]}"
|
|
476
|
+
return f"stage:{node_id}", digest, coordinate
|
|
477
|
+
|
|
478
|
+
def transformation_started(self, node_id: str, operation: str, *, key: str) -> VisualRunEvent:
|
|
479
|
+
object_id, _, coordinate = self._stage_identity(node_id)
|
|
480
|
+
return self._append(
|
|
481
|
+
"transformation.started",
|
|
482
|
+
key,
|
|
483
|
+
object_id=object_id,
|
|
484
|
+
object_version=1,
|
|
485
|
+
previous_object_version=None,
|
|
486
|
+
payload={"operation": operation},
|
|
487
|
+
phase="building",
|
|
488
|
+
stage=coordinate,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
@staticmethod
|
|
492
|
+
def _attempt_identity(attempt: str) -> str:
|
|
493
|
+
return f"attempt:{hashlib.sha256(attempt.encode('utf-8')).hexdigest()[:24]}"
|
|
494
|
+
|
|
495
|
+
def attempt_started(self, attempt: str, *, key: str) -> VisualRunEvent:
|
|
496
|
+
return self._append(
|
|
497
|
+
"phase.started",
|
|
498
|
+
key,
|
|
499
|
+
object_id=self._attempt_identity(attempt),
|
|
500
|
+
object_version=1,
|
|
501
|
+
previous_object_version=None,
|
|
502
|
+
payload={},
|
|
503
|
+
phase="building",
|
|
504
|
+
stage="attempt",
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def attempt_completed(self, attempt: str, *, key: str) -> VisualRunEvent:
|
|
508
|
+
object_id = self._attempt_identity(attempt)
|
|
509
|
+
version, previous = self._update_versions(object_id, key)
|
|
510
|
+
return self._append(
|
|
511
|
+
"phase.completed",
|
|
512
|
+
key,
|
|
513
|
+
object_id=object_id,
|
|
514
|
+
object_version=version,
|
|
515
|
+
previous_object_version=previous,
|
|
516
|
+
payload={},
|
|
517
|
+
phase="building",
|
|
518
|
+
stage="attempt",
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
def attempt_failed(self, attempt: str, error: TerminalError, *, key: str) -> VisualRunEvent:
|
|
522
|
+
object_id = self._attempt_identity(attempt)
|
|
523
|
+
version, previous = self._update_versions(object_id, key)
|
|
524
|
+
return self._append(
|
|
525
|
+
"phase.failed",
|
|
526
|
+
key,
|
|
527
|
+
object_id=object_id,
|
|
528
|
+
object_version=version,
|
|
529
|
+
previous_object_version=previous,
|
|
530
|
+
payload={},
|
|
531
|
+
error=error,
|
|
532
|
+
phase="building",
|
|
533
|
+
stage="attempt",
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
def transformation_completed(
|
|
537
|
+
self, node_id: str, operation: str, row_count: int, *, key: str
|
|
538
|
+
) -> VisualRunEvent:
|
|
539
|
+
object_id, _, coordinate = self._stage_identity(node_id)
|
|
540
|
+
version, previous = self._update_versions(object_id, key)
|
|
541
|
+
return self._append(
|
|
542
|
+
"transformation.completed",
|
|
543
|
+
key,
|
|
544
|
+
object_id=object_id,
|
|
545
|
+
object_version=version,
|
|
546
|
+
previous_object_version=previous,
|
|
547
|
+
payload={"operation": operation, "row_count": row_count},
|
|
548
|
+
phase="building",
|
|
549
|
+
stage=coordinate,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def source_selected(self, source_id: str, source_kind: str, *, key: str) -> VisualRunEvent:
|
|
553
|
+
return self._append(
|
|
554
|
+
"source.selected",
|
|
555
|
+
key,
|
|
556
|
+
object_id=f"source:{source_id}",
|
|
557
|
+
object_version=1,
|
|
558
|
+
previous_object_version=None,
|
|
559
|
+
payload={"source_kind": source_kind},
|
|
560
|
+
phase="researching",
|
|
561
|
+
stage="source-selection",
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
def publish_source_observation(
|
|
565
|
+
self,
|
|
566
|
+
source_id: str,
|
|
567
|
+
observation: Mapping[str, Any],
|
|
568
|
+
*,
|
|
569
|
+
observation_kind: str,
|
|
570
|
+
key: str,
|
|
571
|
+
) -> VisualRunEvent:
|
|
572
|
+
object_id = f"source:{source_id}"
|
|
573
|
+
version, previous = self._update_versions(object_id, key)
|
|
574
|
+
normalized, redacted = scrub_for_display(dict(observation))
|
|
575
|
+
assert isinstance(normalized, dict)
|
|
576
|
+
content = canonical_json_bytes(normalized) + b"\n"
|
|
577
|
+
digest = hashlib.sha256(source_id.encode("utf-8")).hexdigest()
|
|
578
|
+
reference = self._install(
|
|
579
|
+
content,
|
|
580
|
+
artifact_id=f"source-{digest[:24]}-observation-v{version}",
|
|
581
|
+
media_type="application/json",
|
|
582
|
+
artifact_schema="mostlyright.source-observation.v1",
|
|
583
|
+
display="summary" if redacted else "inline",
|
|
584
|
+
projected=True,
|
|
585
|
+
)
|
|
586
|
+
return self._append(
|
|
587
|
+
"source.observation_ready",
|
|
588
|
+
key,
|
|
589
|
+
object_id=object_id,
|
|
590
|
+
object_version=version,
|
|
591
|
+
previous_object_version=previous,
|
|
592
|
+
payload={"observation_kind": observation_kind},
|
|
593
|
+
artifact=reference,
|
|
594
|
+
phase="researching",
|
|
595
|
+
stage="source-inspection",
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
def publish_quality_observation(
|
|
599
|
+
self,
|
|
600
|
+
checks_passed: int,
|
|
601
|
+
checks_total: int,
|
|
602
|
+
*,
|
|
603
|
+
key: str,
|
|
604
|
+
report: Mapping[str, Any] | None = None,
|
|
605
|
+
) -> VisualRunEvent:
|
|
606
|
+
observation = (
|
|
607
|
+
dict(report)
|
|
608
|
+
if report is not None
|
|
609
|
+
else {
|
|
610
|
+
"schema_version": "mostlyright.quality-observation.v1",
|
|
611
|
+
"status": "passed" if checks_passed == checks_total else "failed",
|
|
612
|
+
"checks": [],
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
observation = {
|
|
616
|
+
"schema_version": "mostlyright.quality-observation.v1",
|
|
617
|
+
"checks_passed": checks_passed,
|
|
618
|
+
"checks_total": checks_total,
|
|
619
|
+
"report": observation,
|
|
620
|
+
}
|
|
621
|
+
normalized, redacted = scrub_for_display(observation)
|
|
622
|
+
assert isinstance(normalized, dict)
|
|
623
|
+
content = canonical_json_bytes(normalized) + b"\n"
|
|
624
|
+
reference = self._install(
|
|
625
|
+
content,
|
|
626
|
+
artifact_id="quality-observation",
|
|
627
|
+
media_type="application/json",
|
|
628
|
+
artifact_schema="mostlyright.quality-observation.v1",
|
|
629
|
+
display="summary" if redacted else "inline",
|
|
630
|
+
)
|
|
631
|
+
return self._append(
|
|
632
|
+
"quality.completed",
|
|
633
|
+
key,
|
|
634
|
+
object_id="quality",
|
|
635
|
+
object_version=1,
|
|
636
|
+
previous_object_version=None,
|
|
637
|
+
payload={"checks_passed": checks_passed, "checks_total": checks_total},
|
|
638
|
+
artifact=reference,
|
|
639
|
+
phase="building",
|
|
640
|
+
stage="quality",
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
def table_written(
|
|
644
|
+
self, table_sha256: str, *, row_count: int, column_count: int, key: str
|
|
645
|
+
) -> VisualRunEvent:
|
|
646
|
+
if len(table_sha256) != 64 or any(
|
|
647
|
+
character not in "0123456789abcdef" for character in table_sha256
|
|
648
|
+
):
|
|
649
|
+
raise ValueError("table_sha256 must be lowercase SHA-256")
|
|
650
|
+
content = (
|
|
651
|
+
canonical_json_bytes(
|
|
652
|
+
{
|
|
653
|
+
"schema_version": "mostlyright.table-reference.v1",
|
|
654
|
+
"table_sha256": table_sha256,
|
|
655
|
+
"row_count": row_count,
|
|
656
|
+
"column_count": column_count,
|
|
657
|
+
}
|
|
658
|
+
)
|
|
659
|
+
+ b"\n"
|
|
660
|
+
)
|
|
661
|
+
reference = self._install(
|
|
662
|
+
content,
|
|
663
|
+
artifact_id=f"table-{table_sha256[:24]}",
|
|
664
|
+
media_type="application/json",
|
|
665
|
+
artifact_schema="mostlyright.table-reference.v1",
|
|
666
|
+
display="summary",
|
|
667
|
+
)
|
|
668
|
+
return self._append(
|
|
669
|
+
"table_written",
|
|
670
|
+
key,
|
|
671
|
+
object_id="table",
|
|
672
|
+
object_version=1,
|
|
673
|
+
previous_object_version=None,
|
|
674
|
+
payload={"row_count": row_count, "column_count": column_count},
|
|
675
|
+
artifact=reference,
|
|
676
|
+
phase="building",
|
|
677
|
+
stage="table",
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
def publish_table_observation(
|
|
681
|
+
self, table_sha256: str, *, row_count: int, column_count: int, key: str
|
|
682
|
+
) -> VisualRunEvent:
|
|
683
|
+
return self.table_written(
|
|
684
|
+
table_sha256, row_count=row_count, column_count=column_count, key=key
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
def build_sealed(self, candidate_digest: str, *, key: str) -> VisualRunEvent:
|
|
688
|
+
return self._append(
|
|
689
|
+
"build.sealed",
|
|
690
|
+
key,
|
|
691
|
+
object_id="build",
|
|
692
|
+
object_version=1,
|
|
693
|
+
previous_object_version=None,
|
|
694
|
+
payload={"candidate_digest": candidate_digest},
|
|
695
|
+
phase="building",
|
|
696
|
+
stage="sealing",
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
def build_verified(self, candidate_digest: str, *, key: str) -> VisualRunEvent:
|
|
700
|
+
version, previous = self._update_versions("build", key)
|
|
701
|
+
return self._append(
|
|
702
|
+
"build.verified",
|
|
703
|
+
key,
|
|
704
|
+
object_id="build",
|
|
705
|
+
object_version=version,
|
|
706
|
+
previous_object_version=previous,
|
|
707
|
+
payload={"candidate_digest": candidate_digest},
|
|
708
|
+
phase="building",
|
|
709
|
+
stage="verification",
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
def notebook_materialized(self, kind: str, content: bytes, *, key: str) -> VisualRunEvent:
|
|
713
|
+
if kind not in {"research", "table"}:
|
|
714
|
+
raise ValueError("notebook kind must be research or table")
|
|
715
|
+
digest = hashlib.sha256(content).hexdigest()
|
|
716
|
+
reference = self._install(
|
|
717
|
+
content,
|
|
718
|
+
artifact_id=f"notebook-{kind}-{digest[:24]}",
|
|
719
|
+
media_type="application/x-ipynb+json",
|
|
720
|
+
artifact_schema="mostlyright.notebook.v1",
|
|
721
|
+
display="inline",
|
|
722
|
+
)
|
|
723
|
+
return self._append(
|
|
724
|
+
f"notebook.{kind}_materialized",
|
|
725
|
+
key,
|
|
726
|
+
object_id=f"notebook:{kind}",
|
|
727
|
+
object_version=1,
|
|
728
|
+
previous_object_version=None,
|
|
729
|
+
payload={"notebook_digest": digest},
|
|
730
|
+
artifact=reference,
|
|
731
|
+
phase="building" if kind == "table" else "researching",
|
|
732
|
+
stage="notebook",
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
def complete_run(self, *, key: str) -> VisualRunEvent:
|
|
736
|
+
object_id = self._run_object_id()
|
|
737
|
+
version, previous = self._update_versions(object_id, key)
|
|
738
|
+
return self._append(
|
|
739
|
+
"run.completed",
|
|
740
|
+
key,
|
|
741
|
+
object_id=object_id,
|
|
742
|
+
object_version=version,
|
|
743
|
+
previous_object_version=previous,
|
|
744
|
+
payload={},
|
|
745
|
+
status="completed",
|
|
746
|
+
phase="complete",
|
|
747
|
+
stage="complete",
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
def fail_run(self, error: TerminalError, *, key: str) -> VisualRunEvent:
|
|
751
|
+
object_id = self._run_object_id()
|
|
752
|
+
version, previous = self._update_versions(object_id, key)
|
|
753
|
+
return self._append(
|
|
754
|
+
"run.failed",
|
|
755
|
+
key,
|
|
756
|
+
object_id=object_id,
|
|
757
|
+
object_version=version,
|
|
758
|
+
previous_object_version=previous,
|
|
759
|
+
payload={},
|
|
760
|
+
status="failed",
|
|
761
|
+
error=error,
|
|
762
|
+
phase="failed",
|
|
763
|
+
stage="failed",
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
def publish_stage_observation(
|
|
767
|
+
self,
|
|
768
|
+
observation: Mapping[str, Any],
|
|
769
|
+
*,
|
|
770
|
+
key: str,
|
|
771
|
+
) -> VisualRunEvent:
|
|
772
|
+
"""Persist one deterministic graph-stage observation before candidate sealing."""
|
|
773
|
+
|
|
774
|
+
candidate, redacted = scrub_for_display(dict(observation))
|
|
775
|
+
assert isinstance(candidate, dict)
|
|
776
|
+
if redacted:
|
|
777
|
+
candidate["display"] = "schema_only"
|
|
778
|
+
candidate["sample"] = []
|
|
779
|
+
if "operation_parameters" in candidate:
|
|
780
|
+
candidate["operation_parameters"] = {}
|
|
781
|
+
if "source_coordinates" in candidate:
|
|
782
|
+
candidate["source_coordinates"] = []
|
|
783
|
+
normalized = validate_stage_observation(candidate)
|
|
784
|
+
node_id = str(normalized["node_id"])
|
|
785
|
+
operation = str(normalized["operation"])
|
|
786
|
+
row_count = normalized["output_rows"]
|
|
787
|
+
object_id, node_digest, stage_coordinate = self._stage_identity(node_id)
|
|
788
|
+
if object_id not in self._state().object_versions:
|
|
789
|
+
self.transformation_started(node_id, operation, key=f"{key}:started")
|
|
790
|
+
content = canonical_json_bytes(normalized) + b"\n"
|
|
791
|
+
display = {
|
|
792
|
+
"full": "inline",
|
|
793
|
+
"redacted": "redacted",
|
|
794
|
+
"aggregate_only": "summary",
|
|
795
|
+
"schema_only": "summary",
|
|
796
|
+
}.get(str(normalized["display"]))
|
|
797
|
+
if display is None:
|
|
798
|
+
raise ValueError("stage observation display policy is invalid")
|
|
799
|
+
reference = self._install(
|
|
800
|
+
content,
|
|
801
|
+
artifact_id=f"stage-{node_digest[:24]}-observation",
|
|
802
|
+
media_type="application/json",
|
|
803
|
+
artifact_schema=str(normalized["schema_version"]),
|
|
804
|
+
display=display,
|
|
805
|
+
projected=True,
|
|
806
|
+
)
|
|
807
|
+
version, previous = self._update_versions(object_id, f"{key}:ready")
|
|
808
|
+
return self._append(
|
|
809
|
+
"stage.observation_ready",
|
|
810
|
+
f"{key}:ready",
|
|
811
|
+
object_id=object_id,
|
|
812
|
+
object_version=version,
|
|
813
|
+
previous_object_version=previous,
|
|
814
|
+
payload={"operation": operation, "row_count": row_count},
|
|
815
|
+
artifact=reference,
|
|
816
|
+
phase="building",
|
|
817
|
+
stage=stage_coordinate,
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
def _install(
|
|
821
|
+
self,
|
|
822
|
+
content: bytes,
|
|
823
|
+
*,
|
|
824
|
+
artifact_id: str,
|
|
825
|
+
media_type: str,
|
|
826
|
+
artifact_schema: str,
|
|
827
|
+
display: str = "inline",
|
|
828
|
+
projected: bool = False,
|
|
829
|
+
replacing: ArtifactReference | None = None,
|
|
830
|
+
) -> ArtifactReference:
|
|
831
|
+
if projected:
|
|
832
|
+
state = self._state()
|
|
833
|
+
current_bytes = sum(
|
|
834
|
+
reference.byte_size
|
|
835
|
+
for cell in state.cells.values()
|
|
836
|
+
for reference in ((cell.source,) if cell.source is not None else ()) + cell.outputs
|
|
837
|
+
) + sum(reference.byte_size for reference in state.observations)
|
|
838
|
+
prospective_bytes = current_bytes + len(content)
|
|
839
|
+
if replacing is not None:
|
|
840
|
+
prospective_bytes -= replacing.byte_size
|
|
841
|
+
if prospective_bytes > MAX_MATERIALIZED_NOTEBOOK_BYTES:
|
|
842
|
+
raise VisualRunError(
|
|
843
|
+
"VISUAL_RUN_PROJECTION_LIMIT",
|
|
844
|
+
"event would exceed the materialized research notebook limit",
|
|
845
|
+
)
|
|
846
|
+
if len(content) > MAX_MATERIALIZED_NOTEBOOK_BYTES:
|
|
847
|
+
raise VisualRunError(
|
|
848
|
+
"VISUAL_RUN_PROJECTION_LIMIT",
|
|
849
|
+
"artifact exceeds the materialized research notebook limit",
|
|
850
|
+
)
|
|
851
|
+
reference = ArtifactReference.from_mapping(
|
|
852
|
+
{
|
|
853
|
+
"schema_version": "visual-run-artifact-ref.v2",
|
|
854
|
+
"artifact_id": artifact_id,
|
|
855
|
+
"sha256": hashlib.sha256(content).hexdigest(),
|
|
856
|
+
"media_type": media_type,
|
|
857
|
+
"artifact_schema": artifact_schema,
|
|
858
|
+
"byte_size": len(content),
|
|
859
|
+
"truncated": False,
|
|
860
|
+
"original_byte_size": len(content),
|
|
861
|
+
"display": display,
|
|
862
|
+
}
|
|
863
|
+
)
|
|
864
|
+
self.store.install_artifact(reference, content)
|
|
865
|
+
return reference
|
|
866
|
+
|
|
867
|
+
def _retry_artifact_reference(
|
|
868
|
+
self,
|
|
869
|
+
existing: VisualRunEvent,
|
|
870
|
+
content: bytes,
|
|
871
|
+
*,
|
|
872
|
+
expected_schema: str,
|
|
873
|
+
) -> ArtifactReference:
|
|
874
|
+
"""Rebuild retry intent without consulting mutable state or installing an artifact."""
|
|
875
|
+
|
|
876
|
+
reference = existing.artifact
|
|
877
|
+
if reference is None or reference.artifact_schema != expected_schema:
|
|
878
|
+
raise VisualRunError(
|
|
879
|
+
"VISUAL_RUN_IDEMPOTENCY_CONFLICT",
|
|
880
|
+
"idempotency key names another command",
|
|
881
|
+
)
|
|
882
|
+
return ArtifactReference.from_mapping(
|
|
883
|
+
{
|
|
884
|
+
**reference.to_mapping(),
|
|
885
|
+
"sha256": hashlib.sha256(content).hexdigest(),
|
|
886
|
+
"byte_size": len(content),
|
|
887
|
+
"original_byte_size": len(content),
|
|
888
|
+
}
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
__all__ = ["VisualWork", "attempt_event_key", "next_attempt_coordinate"]
|