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,546 @@
|
|
|
1
|
+
"""The local append-only empirical observation history one operator retains for one source.
|
|
2
|
+
|
|
3
|
+
Hosted probes append to a chain Studio authenticates and persists. A local probe has no Studio,
|
|
4
|
+
so this module is the local coordinator's equivalent: one folder per source epoch, one immutable
|
|
5
|
+
canonical record per probe, and the same digest chain the contract validates.
|
|
6
|
+
|
|
7
|
+
Three properties are the point, and each is enforced rather than described. The history is
|
|
8
|
+
append-only, because a probe record is created with ``O_EXCL`` and a sequence that already exists
|
|
9
|
+
is a refusal rather than an overwrite. It is canonical, because every record is the exact
|
|
10
|
+
canonical JSON bytes of its document and is re-parsed through the contract on the way back in.
|
|
11
|
+
It is digest-bound, because each record carries the observation, the observation's digest, and
|
|
12
|
+
the probe receipt whose digest that observation names -- so a record that was edited no longer
|
|
13
|
+
verifies against itself, and a record moved between histories no longer verifies against its
|
|
14
|
+
predecessor.
|
|
15
|
+
|
|
16
|
+
The stored authority is what fixes the epoch. The contract requires a new inference epoch when
|
|
17
|
+
the connector configuration, locator, program, source authority, Reader, Reader options, frontier
|
|
18
|
+
derivation, time interpretation, or algorithm changes, so a history whose authority no longer
|
|
19
|
+
matches the one this probe was given is refused whole rather than continued. Two epochs never
|
|
20
|
+
enter one inference.
|
|
21
|
+
|
|
22
|
+
This module reads no clock and no network. It grants nothing: a local history is evidence an
|
|
23
|
+
operator retained, not a release, an approval, or a schedule.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import stat
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from mostlyright.data_harness.canonical import (
|
|
35
|
+
CanonicalJSONError,
|
|
36
|
+
canonical_json_bytes,
|
|
37
|
+
canonical_sha256,
|
|
38
|
+
parse_canonical_json,
|
|
39
|
+
)
|
|
40
|
+
from mostlyright.data_harness.sources.cadence import (
|
|
41
|
+
CadenceContractError,
|
|
42
|
+
EmpiricalSourceObservation,
|
|
43
|
+
parse_empirical_source_observation,
|
|
44
|
+
validate_observation_history,
|
|
45
|
+
)
|
|
46
|
+
from mostlyright.data_harness.sources.cadence_emission import (
|
|
47
|
+
SOURCE_CADENCE_AUTHORITY_FIELDS,
|
|
48
|
+
SourceCadenceAuthority,
|
|
49
|
+
SourceCadenceEmissionError,
|
|
50
|
+
)
|
|
51
|
+
from mostlyright.data_harness.sources.contracts import (
|
|
52
|
+
AcquisitionRequest,
|
|
53
|
+
decode_options_digest,
|
|
54
|
+
)
|
|
55
|
+
from mostlyright.data_harness.ux.plain_file import (
|
|
56
|
+
MISSING,
|
|
57
|
+
PlainFileRefusal,
|
|
58
|
+
read_plain_file,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
LOCAL_CADENCE_AUTHORITY_SCHEMA = "harness-local-source-cadence-authority.v1"
|
|
62
|
+
LOCAL_CADENCE_RECORD_SCHEMA = "harness-local-source-cadence-record.v1"
|
|
63
|
+
LOCAL_CADENCE_PROBE_REFUSAL_SCHEMA = "harness-local-source-probe-refusal.v1"
|
|
64
|
+
|
|
65
|
+
#: The contract's own ceiling on one inference history. Refusing at the same number keeps a
|
|
66
|
+
#: folder that can still be read by ``infer_source_cadence`` from being written past the point
|
|
67
|
+
#: where it cannot.
|
|
68
|
+
MAX_LOCAL_OBSERVATIONS = 4_096
|
|
69
|
+
_AUTHORITY_NAME = "authority.json"
|
|
70
|
+
_OBSERVATIONS_NAME = "observations"
|
|
71
|
+
_RECORD_KEYS = frozenset({"schema_version", "observation", "observation_digest", "probe_receipt"})
|
|
72
|
+
# A record is small: one observation, one receipt. The ceiling is a bound on what this reads
|
|
73
|
+
# from a folder another local user may also write to, not a size anything here aims for.
|
|
74
|
+
_MAX_RECORD_BYTES = 1 << 20
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class LocalCadenceHistoryError(ValueError):
|
|
78
|
+
"""Typed refusal raised by the local observation history."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, code: str, detail: str) -> None:
|
|
81
|
+
self.code = code
|
|
82
|
+
self.detail = detail
|
|
83
|
+
super().__init__(f"{detail} [{code}]")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True)
|
|
87
|
+
class LocalObservationHistory:
|
|
88
|
+
"""One epoch's stored authority and its complete validated observation chain."""
|
|
89
|
+
|
|
90
|
+
directory: Path
|
|
91
|
+
authority: SourceCadenceAuthority
|
|
92
|
+
observations: tuple[EmpiricalSourceObservation, ...]
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def next_sequence(self) -> int:
|
|
96
|
+
return len(self.observations)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def previous_observation_digest(self) -> str | None:
|
|
100
|
+
return self.observations[-1].digest if self.observations else None
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def latest_acquired(self) -> EmpiricalSourceObservation | None:
|
|
104
|
+
"""Return the newest observation that acquired bytes, which is the change baseline.
|
|
105
|
+
|
|
106
|
+
A probe that acquired nothing proves nothing about the content, so it can neither be a
|
|
107
|
+
baseline nor erase one. Reading backwards for the newest acquired fact is what lets a
|
|
108
|
+
failed probe sit between two acquisitions without turning the next one into a change.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
for observation in reversed(self.observations):
|
|
112
|
+
if observation.content_digest is not None:
|
|
113
|
+
return observation
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def parse_local_cadence_authority(value: Any) -> SourceCadenceAuthority:
|
|
118
|
+
"""Parse one operator-stated local cadence authority document."""
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
return SourceCadenceAuthority.from_mapping(
|
|
122
|
+
value,
|
|
123
|
+
schema_version=LOCAL_CADENCE_AUTHORITY_SCHEMA,
|
|
124
|
+
)
|
|
125
|
+
except SourceCadenceEmissionError as error:
|
|
126
|
+
raise LocalCadenceHistoryError("CADENCE_AUTHORITY_INVALID", error.detail) from error
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def require_authority_binds_request(
|
|
130
|
+
authority: SourceCadenceAuthority,
|
|
131
|
+
request: AcquisitionRequest,
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Refuse an authority that does not describe the acquisition it is recording.
|
|
134
|
+
|
|
135
|
+
A local coordinator states the epoch identity, exactly as Studio states it hosted. Two of
|
|
136
|
+
its eleven fields are not statements at all locally, because this side holds the acquisition
|
|
137
|
+
document and can check them. The locator is the canonical query digest: an authority whose
|
|
138
|
+
locator names another address would splice two sources into one history, and the inference
|
|
139
|
+
would read a cadence neither publisher has. The Reader coordinate is the pinned decoder:
|
|
140
|
+
an authority naming a Reader the request did not entitle would attribute this epoch's parse
|
|
141
|
+
to code that never ran.
|
|
142
|
+
|
|
143
|
+
The remaining fields stay statements this side takes as given, the way the hosted producer
|
|
144
|
+
takes Studio's. Recording that difference is the point of checking the two that can be
|
|
145
|
+
checked rather than none.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
if authority.locator_digest != request.query_digest:
|
|
149
|
+
raise LocalCadenceHistoryError(
|
|
150
|
+
"CADENCE_AUTHORITY_LOCATOR",
|
|
151
|
+
"the cadence authority names a locator other than this request's exact query",
|
|
152
|
+
)
|
|
153
|
+
pin = request.reader_pin
|
|
154
|
+
if pin is None:
|
|
155
|
+
return
|
|
156
|
+
if (
|
|
157
|
+
authority.reader_family_id != pin["family_id"]
|
|
158
|
+
or authority.reader_family_version != pin["family_version"]
|
|
159
|
+
or authority.reader_options_digest != decode_options_digest(pin["decode_options"])
|
|
160
|
+
):
|
|
161
|
+
raise LocalCadenceHistoryError(
|
|
162
|
+
"CADENCE_AUTHORITY_READER",
|
|
163
|
+
"the cadence authority names a Reader coordinate this request did not entitle",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def probe_refusal_document(
|
|
168
|
+
*,
|
|
169
|
+
source_id: str,
|
|
170
|
+
request_digest: str,
|
|
171
|
+
query_digest: str,
|
|
172
|
+
acquisition_environment: str,
|
|
173
|
+
clean_room_attestation_digest: str,
|
|
174
|
+
external_network_policy_attestation: str,
|
|
175
|
+
refusal_code: str,
|
|
176
|
+
probed_at: str,
|
|
177
|
+
) -> dict[str, Any]:
|
|
178
|
+
"""Seal what a refused probe proves, so its observation has a receipt to bind.
|
|
179
|
+
|
|
180
|
+
Every observation names a probe receipt, because evidence that a probe happened is what
|
|
181
|
+
separates a recorded absence from an assumption. A refused probe acquired nothing, so there
|
|
182
|
+
is no acquisition receipt to name and this document is the receipt: the exact request, the
|
|
183
|
+
confinement it ran in, and the typed code the source interaction returned. It holds no
|
|
184
|
+
response body, no header text, and no acquired fact, because the probe obtained none.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
"schema_version": LOCAL_CADENCE_PROBE_REFUSAL_SCHEMA,
|
|
189
|
+
"source_id": source_id,
|
|
190
|
+
"request_digest": request_digest,
|
|
191
|
+
"query_digest": query_digest,
|
|
192
|
+
"acquisition_environment": acquisition_environment,
|
|
193
|
+
"clean_room_attestation_digest": clean_room_attestation_digest,
|
|
194
|
+
"external_network_policy_attestation": external_network_policy_attestation,
|
|
195
|
+
"refusal_code": refusal_code,
|
|
196
|
+
"probed_at": probed_at,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def open_local_history(
|
|
201
|
+
directory: Path,
|
|
202
|
+
*,
|
|
203
|
+
authority: SourceCadenceAuthority | None,
|
|
204
|
+
) -> LocalObservationHistory:
|
|
205
|
+
"""Open or start one local history and return its complete validated chain.
|
|
206
|
+
|
|
207
|
+
``authority`` is the authority this probe was given. A history that already holds one keeps
|
|
208
|
+
it: the stored bytes decide, and a supplied authority that differs is refused rather than
|
|
209
|
+
merged, because a differing authority is a different epoch and the contract never combines
|
|
210
|
+
two epochs into one inference.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
root = _require_history_directory(directory)
|
|
214
|
+
stored = _read_authority(root)
|
|
215
|
+
if stored is None:
|
|
216
|
+
if authority is None:
|
|
217
|
+
raise LocalCadenceHistoryError(
|
|
218
|
+
"CADENCE_AUTHORITY_REQUIRED",
|
|
219
|
+
"this history holds no authority yet, so the first probe has to state one",
|
|
220
|
+
)
|
|
221
|
+
try:
|
|
222
|
+
_write_exclusive(root / _AUTHORITY_NAME, canonical_json_bytes(authority.to_dict()))
|
|
223
|
+
except FileExistsError as error:
|
|
224
|
+
raise LocalCadenceHistoryError(
|
|
225
|
+
"CADENCE_AUTHORITY_EXISTS",
|
|
226
|
+
"another probe stated this history's epoch identity first",
|
|
227
|
+
) from error
|
|
228
|
+
stored = authority
|
|
229
|
+
elif authority is not None and authority.to_dict() != stored.to_dict():
|
|
230
|
+
raise LocalCadenceHistoryError(
|
|
231
|
+
"CADENCE_AUTHORITY_CHANGED",
|
|
232
|
+
"this history was recorded under a different epoch identity",
|
|
233
|
+
)
|
|
234
|
+
observations = _read_observations(root, stored)
|
|
235
|
+
return LocalObservationHistory(directory=root, authority=stored, observations=observations)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def append_local_observation(
|
|
239
|
+
history: LocalObservationHistory,
|
|
240
|
+
observation: EmpiricalSourceObservation,
|
|
241
|
+
probe_receipt: dict[str, Any],
|
|
242
|
+
) -> LocalObservationHistory:
|
|
243
|
+
"""Append one probe record and return the extended, revalidated history."""
|
|
244
|
+
|
|
245
|
+
if observation.sequence != history.next_sequence:
|
|
246
|
+
raise LocalCadenceHistoryError(
|
|
247
|
+
"CADENCE_HISTORY_SEQUENCE",
|
|
248
|
+
"the observation does not continue this history",
|
|
249
|
+
)
|
|
250
|
+
if len(history.observations) >= MAX_LOCAL_OBSERVATIONS:
|
|
251
|
+
raise LocalCadenceHistoryError(
|
|
252
|
+
"CADENCE_HISTORY_FULL",
|
|
253
|
+
"this history already holds every observation one inference may read",
|
|
254
|
+
)
|
|
255
|
+
_require_no_record_beyond(history)
|
|
256
|
+
receipt_digest = canonical_sha256(probe_receipt)
|
|
257
|
+
if receipt_digest != observation.probe_receipt_digest:
|
|
258
|
+
raise LocalCadenceHistoryError(
|
|
259
|
+
"CADENCE_RECEIPT_BINDING",
|
|
260
|
+
"the observation does not name the probe receipt recorded beside it",
|
|
261
|
+
)
|
|
262
|
+
extended = (*history.observations, observation)
|
|
263
|
+
try:
|
|
264
|
+
validate_observation_history(extended)
|
|
265
|
+
except CadenceContractError as error:
|
|
266
|
+
raise LocalCadenceHistoryError("CADENCE_HISTORY_INVALID", error.detail) from error
|
|
267
|
+
record = {
|
|
268
|
+
"schema_version": LOCAL_CADENCE_RECORD_SCHEMA,
|
|
269
|
+
"observation": observation.to_dict(),
|
|
270
|
+
"observation_digest": observation.digest,
|
|
271
|
+
"probe_receipt": dict(probe_receipt),
|
|
272
|
+
}
|
|
273
|
+
observations_root = history.directory / _OBSERVATIONS_NAME
|
|
274
|
+
_make_directory(observations_root)
|
|
275
|
+
try:
|
|
276
|
+
_write_exclusive(
|
|
277
|
+
observations_root / _record_name(observation.sequence),
|
|
278
|
+
canonical_json_bytes(record),
|
|
279
|
+
)
|
|
280
|
+
except FileExistsError as error:
|
|
281
|
+
raise LocalCadenceHistoryError(
|
|
282
|
+
"CADENCE_RECORD_EXISTS",
|
|
283
|
+
"another probe was recorded into this history while this one ran",
|
|
284
|
+
) from error
|
|
285
|
+
return LocalObservationHistory(
|
|
286
|
+
directory=history.directory,
|
|
287
|
+
authority=history.authority,
|
|
288
|
+
observations=extended,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _require_no_record_beyond(history: LocalObservationHistory) -> None:
|
|
293
|
+
"""Refuse to append when the folder holds a record this history did not read.
|
|
294
|
+
|
|
295
|
+
A read stops at the first missing sequence, which leaves two ways the folder can hold more
|
|
296
|
+
than the history does, and both are refusals.
|
|
297
|
+
|
|
298
|
+
A record at exactly the next coordinate appeared after this history was read, which is a
|
|
299
|
+
second probe of the same source running at the same time. A record *past* it means the read
|
|
300
|
+
stopped at a gap -- an entry removed or never written -- while later entries are still on
|
|
301
|
+
disk. Appending into that gap would write a new sequence the survivors no longer bind, and
|
|
302
|
+
every read afterwards would refuse the chain permanently, because the predecessor they name
|
|
303
|
+
would never exist again.
|
|
304
|
+
|
|
305
|
+
The folder is read once rather than one coordinate probed, because the gap case is exactly
|
|
306
|
+
the one a single probe of the next name cannot see.
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
observations_root = history.directory / _OBSERVATIONS_NAME
|
|
310
|
+
try:
|
|
311
|
+
names = {item.name for item in observations_root.iterdir()}
|
|
312
|
+
except FileNotFoundError:
|
|
313
|
+
return
|
|
314
|
+
except OSError as error:
|
|
315
|
+
raise LocalCadenceHistoryError(
|
|
316
|
+
"CADENCE_HISTORY_UNREADABLE",
|
|
317
|
+
f"the cadence history folder could not be read: {error.strerror or error}",
|
|
318
|
+
) from error
|
|
319
|
+
beyond = [
|
|
320
|
+
sequence
|
|
321
|
+
for sequence in range(history.next_sequence, MAX_LOCAL_OBSERVATIONS + 1)
|
|
322
|
+
if _record_name(sequence) in names
|
|
323
|
+
]
|
|
324
|
+
if not beyond:
|
|
325
|
+
return
|
|
326
|
+
if beyond == [history.next_sequence]:
|
|
327
|
+
raise LocalCadenceHistoryError(
|
|
328
|
+
"CADENCE_RECORD_EXISTS",
|
|
329
|
+
"another probe was recorded into this history while this one ran",
|
|
330
|
+
)
|
|
331
|
+
raise LocalCadenceHistoryError(
|
|
332
|
+
"CADENCE_HISTORY_INCOMPLETE",
|
|
333
|
+
"this history holds records past the point it can be read from",
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _record_name(sequence: int) -> str:
|
|
338
|
+
return f"{sequence:010d}.json"
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _require_history_directory(directory: Path) -> Path:
|
|
342
|
+
root = directory.absolute()
|
|
343
|
+
_make_directory(root)
|
|
344
|
+
return root
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _make_directory(path: Path) -> None:
|
|
348
|
+
try:
|
|
349
|
+
path.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
350
|
+
except OSError as error:
|
|
351
|
+
raise LocalCadenceHistoryError(
|
|
352
|
+
"CADENCE_HISTORY_UNWRITABLE",
|
|
353
|
+
f"the cadence history folder could not be created: {error.strerror or error}",
|
|
354
|
+
) from error
|
|
355
|
+
# ``lstat`` rather than ``stat``: a link that points at a folder reports a folder through
|
|
356
|
+
# the link, and this has to be a folder this command created or already owns. One test is
|
|
357
|
+
# enough -- a mode is a link or a folder and never both -- so a second ``S_ISLNK`` clause
|
|
358
|
+
# here would be a guard that can never fire.
|
|
359
|
+
if not stat.S_ISDIR(path.lstat().st_mode):
|
|
360
|
+
raise LocalCadenceHistoryError(
|
|
361
|
+
"CADENCE_HISTORY_UNWRITABLE",
|
|
362
|
+
"the cadence history path is not a folder this command can own",
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _write_exclusive(path: Path, payload: bytes) -> None:
|
|
367
|
+
"""Create one file, or let the collision reach the caller that can name it.
|
|
368
|
+
|
|
369
|
+
``O_EXCL`` is the append-only rule. A name that already exists was written by somebody --
|
|
370
|
+
a second probe racing this one, or a second first probe of the same history -- and both are
|
|
371
|
+
refusals rather than overwrites, because what is already there is somebody's evidence.
|
|
372
|
+
|
|
373
|
+
``FileExistsError`` is deliberately not turned into a typed refusal here. An authority
|
|
374
|
+
written twice and a record written twice are different events with different repairs, so
|
|
375
|
+
each caller raises its own code, written as a literal at its own raise. A code passed in as
|
|
376
|
+
an argument would be a code the repository's typed-code completeness gates cannot see, and
|
|
377
|
+
this repository has been caught by that shape twice already.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
382
|
+
except OSError as error:
|
|
383
|
+
raise LocalCadenceHistoryError(
|
|
384
|
+
"CADENCE_HISTORY_UNWRITABLE",
|
|
385
|
+
f"the cadence record could not be created: {error.strerror or error}",
|
|
386
|
+
) from error
|
|
387
|
+
try:
|
|
388
|
+
with os.fdopen(descriptor, "wb") as handle:
|
|
389
|
+
handle.write(payload)
|
|
390
|
+
handle.flush()
|
|
391
|
+
os.fsync(handle.fileno())
|
|
392
|
+
except OSError as error:
|
|
393
|
+
raise LocalCadenceHistoryError(
|
|
394
|
+
"CADENCE_HISTORY_UNWRITABLE",
|
|
395
|
+
f"the cadence record could not be written: {error.strerror or error}",
|
|
396
|
+
) from error
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _read_document(path: Path) -> Any:
|
|
400
|
+
"""Read one stored record, under the open-then-fstat rule every named file is read by.
|
|
401
|
+
|
|
402
|
+
A history folder is an ordinary folder another local user may also write to, so a record is
|
|
403
|
+
read as a bounded plain file rather than by size-then-read, and the bytes have to be the exact
|
|
404
|
+
canonical encoding of what they decode to. A record that is not is not evidence: canonical
|
|
405
|
+
bytes are what its digests were taken over.
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
try:
|
|
409
|
+
raw = read_plain_file(path, max_bytes=_MAX_RECORD_BYTES)
|
|
410
|
+
except PlainFileRefusal as refusal:
|
|
411
|
+
if refusal.reason == MISSING:
|
|
412
|
+
return None
|
|
413
|
+
raise LocalCadenceHistoryError(
|
|
414
|
+
"CADENCE_HISTORY_UNREADABLE",
|
|
415
|
+
f"a cadence history entry is {refusal.reason} and cannot be read as a record",
|
|
416
|
+
) from None
|
|
417
|
+
except OSError as error:
|
|
418
|
+
raise LocalCadenceHistoryError(
|
|
419
|
+
"CADENCE_HISTORY_UNREADABLE",
|
|
420
|
+
f"a cadence history file could not be read: {error.strerror or error}",
|
|
421
|
+
) from error
|
|
422
|
+
try:
|
|
423
|
+
value = parse_canonical_json(raw)
|
|
424
|
+
except CanonicalJSONError as error:
|
|
425
|
+
raise LocalCadenceHistoryError(
|
|
426
|
+
"CADENCE_HISTORY_UNREADABLE",
|
|
427
|
+
"a cadence history file is not the exact canonical bytes it was written as",
|
|
428
|
+
) from error
|
|
429
|
+
if not isinstance(value, dict):
|
|
430
|
+
raise LocalCadenceHistoryError(
|
|
431
|
+
"CADENCE_HISTORY_UNREADABLE",
|
|
432
|
+
"a cadence history file does not hold a document",
|
|
433
|
+
)
|
|
434
|
+
return value
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _read_authority(root: Path) -> SourceCadenceAuthority | None:
|
|
438
|
+
value = _read_document(root / _AUTHORITY_NAME)
|
|
439
|
+
if value is None:
|
|
440
|
+
return None
|
|
441
|
+
if set(value) != SOURCE_CADENCE_AUTHORITY_FIELDS:
|
|
442
|
+
raise LocalCadenceHistoryError(
|
|
443
|
+
"CADENCE_AUTHORITY_INVALID",
|
|
444
|
+
"the stored cadence authority does not have the exact versioned key set",
|
|
445
|
+
)
|
|
446
|
+
return parse_local_cadence_authority(value)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _read_observations(
|
|
450
|
+
root: Path,
|
|
451
|
+
authority: SourceCadenceAuthority,
|
|
452
|
+
) -> tuple[EmpiricalSourceObservation, ...]:
|
|
453
|
+
"""Re-admit every stored record in sequence and validate the chain it forms.
|
|
454
|
+
|
|
455
|
+
Names are derived rather than listed: a record is read at the coordinate it must occupy, so
|
|
456
|
+
a file dropped into the folder under some other name is not evidence and a gap ends the
|
|
457
|
+
history rather than being skipped over.
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
observations_root = root / _OBSERVATIONS_NAME
|
|
461
|
+
if not observations_root.exists():
|
|
462
|
+
return ()
|
|
463
|
+
observations: list[EmpiricalSourceObservation] = []
|
|
464
|
+
while len(observations) < MAX_LOCAL_OBSERVATIONS:
|
|
465
|
+
value = _read_document(observations_root / _record_name(len(observations)))
|
|
466
|
+
if value is None:
|
|
467
|
+
break
|
|
468
|
+
observations.append(_parse_record(value, sequence=len(observations), authority=authority))
|
|
469
|
+
if (observations_root / _record_name(len(observations))).exists():
|
|
470
|
+
raise LocalCadenceHistoryError(
|
|
471
|
+
"CADENCE_HISTORY_FULL",
|
|
472
|
+
"this history holds more observations than one inference may read",
|
|
473
|
+
)
|
|
474
|
+
try:
|
|
475
|
+
return validate_observation_history(tuple(observations))
|
|
476
|
+
except CadenceContractError as error:
|
|
477
|
+
raise LocalCadenceHistoryError("CADENCE_HISTORY_INVALID", error.detail) from error
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _parse_record(
|
|
481
|
+
value: dict[str, Any],
|
|
482
|
+
*,
|
|
483
|
+
sequence: int,
|
|
484
|
+
authority: SourceCadenceAuthority,
|
|
485
|
+
) -> EmpiricalSourceObservation:
|
|
486
|
+
if set(value) != _RECORD_KEYS or value["schema_version"] != LOCAL_CADENCE_RECORD_SCHEMA:
|
|
487
|
+
raise LocalCadenceHistoryError(
|
|
488
|
+
"CADENCE_RECORD_INVALID",
|
|
489
|
+
"a cadence record does not have the exact versioned key set",
|
|
490
|
+
)
|
|
491
|
+
receipt = value["probe_receipt"]
|
|
492
|
+
if not isinstance(receipt, dict):
|
|
493
|
+
raise LocalCadenceHistoryError(
|
|
494
|
+
"CADENCE_RECORD_INVALID",
|
|
495
|
+
"a cadence record carries no probe receipt",
|
|
496
|
+
)
|
|
497
|
+
try:
|
|
498
|
+
observation = parse_empirical_source_observation(value["observation"])
|
|
499
|
+
except (CadenceContractError, TypeError, ValueError) as error:
|
|
500
|
+
raise LocalCadenceHistoryError(
|
|
501
|
+
"CADENCE_RECORD_INVALID",
|
|
502
|
+
"a cadence record does not hold an exact observation",
|
|
503
|
+
) from error
|
|
504
|
+
if observation.digest != value["observation_digest"] or observation.sequence != sequence:
|
|
505
|
+
raise LocalCadenceHistoryError(
|
|
506
|
+
"CADENCE_RECORD_INVALID",
|
|
507
|
+
"a cadence record does not bind the observation stored in it",
|
|
508
|
+
)
|
|
509
|
+
if canonical_sha256(receipt) != observation.probe_receipt_digest:
|
|
510
|
+
raise LocalCadenceHistoryError(
|
|
511
|
+
"CADENCE_RECEIPT_BINDING",
|
|
512
|
+
"a cadence record does not name the probe receipt stored beside it",
|
|
513
|
+
)
|
|
514
|
+
if (
|
|
515
|
+
observation.inference_epoch != authority.inference_epoch
|
|
516
|
+
or observation.connector_configuration_digest != authority.connector_configuration_digest
|
|
517
|
+
or observation.locator_digest != authority.locator_digest
|
|
518
|
+
or observation.recipe_digest != authority.recipe_digest
|
|
519
|
+
or observation.source_authority_digest != authority.source_authority_digest
|
|
520
|
+
or observation.reader_family_id != authority.reader_family_id
|
|
521
|
+
or observation.reader_family_version != authority.reader_family_version
|
|
522
|
+
or observation.reader_options_digest != authority.reader_options_digest
|
|
523
|
+
or observation.frontier_policy_digest != authority.frontier_policy_digest
|
|
524
|
+
or observation.time_interpretation_digest != authority.time_interpretation_digest
|
|
525
|
+
or observation.inference_algorithm_digest != authority.inference_algorithm_digest
|
|
526
|
+
):
|
|
527
|
+
raise LocalCadenceHistoryError(
|
|
528
|
+
"CADENCE_RECORD_INVALID",
|
|
529
|
+
"a cadence record was recorded under a different epoch identity",
|
|
530
|
+
)
|
|
531
|
+
return observation
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
__all__ = [
|
|
535
|
+
"LOCAL_CADENCE_AUTHORITY_SCHEMA",
|
|
536
|
+
"LOCAL_CADENCE_PROBE_REFUSAL_SCHEMA",
|
|
537
|
+
"LOCAL_CADENCE_RECORD_SCHEMA",
|
|
538
|
+
"MAX_LOCAL_OBSERVATIONS",
|
|
539
|
+
"LocalCadenceHistoryError",
|
|
540
|
+
"LocalObservationHistory",
|
|
541
|
+
"append_local_observation",
|
|
542
|
+
"open_local_history",
|
|
543
|
+
"parse_local_cadence_authority",
|
|
544
|
+
"probe_refusal_document",
|
|
545
|
+
"require_authority_binds_request",
|
|
546
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Source-catalog subpackage: versioned facts about public sources.
|
|
2
|
+
|
|
3
|
+
This package intentionally carries **no re-exports**. Consumers import the concrete module they
|
|
4
|
+
need (``...sources.catalog.contracts``, and later ``retrieval`` and ``gating``). Two reasons:
|
|
5
|
+
|
|
6
|
+
1. Retrieval and gating must never share a namespace. Similarity *finds* candidates; structured
|
|
7
|
+
facts *gate* decisions. If both were re-exported here, a gating caller would sit one attribute
|
|
8
|
+
access away from a similarity score, and the separation would become a convention rather than
|
|
9
|
+
a module boundary.
|
|
10
|
+
2. A package ``__init__`` that names every module is a single contended file. Keeping it empty of
|
|
11
|
+
imports lets the catalog modules land independently.
|
|
12
|
+
|
|
13
|
+
Everything in this package describes **public** sources only. No module here may carry a dataset,
|
|
14
|
+
a recipe, a Reader, or anything learned from a tenant's private source.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|