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,1838 @@
|
|
|
1
|
+
"""``mr-data catalog-publish``: classify one authored generation and publish it, once.
|
|
2
|
+
|
|
3
|
+
The third and last provider-scale mutation. It is handed the two receipts the first two stages
|
|
4
|
+
wrote, together with the fingerprints of their exact bytes, and it refuses to start unless those
|
|
5
|
+
two documents describe one staging checkpoint and one authored generation that is complete. It
|
|
6
|
+
then classifies the sweep against its predecessor identity/history, publishes one range-local
|
|
7
|
+
packed generation, has an independent final reader scan the installed generation back off disk,
|
|
8
|
+
and writes one composite receipt.
|
|
9
|
+
|
|
10
|
+
That composite receipt is the authoritative evidence. It *carries* the exact harvest and author
|
|
11
|
+
receipts it was handed rather than a summary of them, binds the execution coordinate, the staging
|
|
12
|
+
indexes, the policy, the classification, every range, vector, posting and bound digest the
|
|
13
|
+
publication produced, the ordered per-invocation counter records and their totals, the expected
|
|
14
|
+
final head, and the independent readback. Downstream verification reads that document; it never
|
|
15
|
+
reconstructs one from counts.
|
|
16
|
+
|
|
17
|
+
Carrying a receipt is not believing it. Every summary in the author receipt is re-derived from
|
|
18
|
+
the authored manifest and the registered policy, and the three members that are not summaries --
|
|
19
|
+
the retained work journal's fingerprint, the bounds the authoring invocation ran under, and the
|
|
20
|
+
bytes it wrote -- are reconciled against the journal and the shards still on disk in the author
|
|
21
|
+
root, as far as retained evidence reaches. It reaches an exact value for one of them, an
|
|
22
|
+
interval for another, and a floor for the rest; the composite states which of those it got for
|
|
23
|
+
each, rather than carrying any of them as though this command had established it.
|
|
24
|
+
|
|
25
|
+
A publication that stops on a bound its caller stated, or on an operator's interruption, writes
|
|
26
|
+
the other receipt this command can write: one typed-incomplete composite receipt bound to the work
|
|
27
|
+
journal the publisher left behind, to this output root, to the ranges that are already immutable,
|
|
28
|
+
and to the ordered per-invocation counter records the work got as far as. ``--resume`` validates
|
|
29
|
+
exactly that receipt, requires the journal on disk to still be the one it names, continues the
|
|
30
|
+
publisher's own resume path, and replaces the receipt with the complete one.
|
|
31
|
+
|
|
32
|
+
A publication that is *killed* rather than stopped -- power, sleep, the kernel -- never writes
|
|
33
|
+
that receipt, because nothing in it runs again. Before the packed head becomes visible, the
|
|
34
|
+
publisher installs a terminal record that the head binds and that captures the exact final journal
|
|
35
|
+
and packed-receipt inputs. ``--resume`` can therefore adopt either unfinished range work or a
|
|
36
|
+
published head whose composite receipt is still absent, after requiring the retained state to be
|
|
37
|
+
this argv's exact publication. The composite an adopted resume ends with is the same document any
|
|
38
|
+
other publication writes. A resume with no retained publication is refused rather than started,
|
|
39
|
+
and a complete receipt is never replaced.
|
|
40
|
+
|
|
41
|
+
This command reaches no network. It takes no key, no endpoint, no URL and no header, and its
|
|
42
|
+
import closure holds nothing from ``acquisition``.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import argparse
|
|
48
|
+
import contextlib
|
|
49
|
+
import os
|
|
50
|
+
from collections.abc import Mapping, Sequence
|
|
51
|
+
from datetime import UTC, datetime
|
|
52
|
+
from pathlib import Path
|
|
53
|
+
from typing import Any
|
|
54
|
+
|
|
55
|
+
from mostlyright.data_harness.canonical import canonical_sha256, sha256_bytes
|
|
56
|
+
from mostlyright.data_harness.sources.catalog.authoring_policy import resolve_authoring_policy
|
|
57
|
+
from mostlyright.data_harness.sources.catalog.authoring_shards import (
|
|
58
|
+
AUTHORING_JOURNAL_SCHEMA,
|
|
59
|
+
AUTHORING_MANIFEST_SCHEMA,
|
|
60
|
+
AUTHORING_RECEIPT_SCHEMA,
|
|
61
|
+
JOURNAL_FILENAME,
|
|
62
|
+
MANIFEST_FILENAME,
|
|
63
|
+
MAX_AUTHORING_JOURNAL_BYTES,
|
|
64
|
+
MAX_AUTHORING_MANIFEST_BYTES,
|
|
65
|
+
MAX_AUTHORING_SHARD_BYTES,
|
|
66
|
+
MAX_AUTHORING_SHARD_ENTRIES,
|
|
67
|
+
shard_descriptor_is_valid,
|
|
68
|
+
)
|
|
69
|
+
from mostlyright.data_harness.sources.catalog.contracts import EMBEDDING_LAYERS
|
|
70
|
+
from mostlyright.data_harness.sources.catalog.coverage import coverage_is_valid
|
|
71
|
+
from mostlyright.data_harness.sources.catalog.embedding import LexicalHashingBackend
|
|
72
|
+
from mostlyright.data_harness.sources.catalog.generation_receipt import (
|
|
73
|
+
GENERATION_RECEIPT_SCHEMA,
|
|
74
|
+
HARVEST_RECEIPT_SCHEMA,
|
|
75
|
+
MAX_COMPOSITE_ARRAY_ITEMS,
|
|
76
|
+
MAX_COMPOSITE_RECEIPT_BYTES,
|
|
77
|
+
MAX_PREDECESSOR_HEAD_BYTES,
|
|
78
|
+
MAX_PREDECESSOR_HEAD_DEPTH,
|
|
79
|
+
MAX_PREDECESSOR_RANGE_DESCRIPTORS,
|
|
80
|
+
MAX_STAGE_RECEIPT_MEMBERS,
|
|
81
|
+
ReceiptInstallation,
|
|
82
|
+
compose_generation_receipt,
|
|
83
|
+
install_receipt,
|
|
84
|
+
is_digest,
|
|
85
|
+
load_bounded_json,
|
|
86
|
+
load_bounded_json_at,
|
|
87
|
+
load_execution_coordinate,
|
|
88
|
+
load_stage_receipt,
|
|
89
|
+
publication_output_lock,
|
|
90
|
+
receipt_installation_lock,
|
|
91
|
+
require_output_root_identity,
|
|
92
|
+
retained_child_directory,
|
|
93
|
+
retained_incomplete_receipt,
|
|
94
|
+
retained_input_directory,
|
|
95
|
+
root_coordinate,
|
|
96
|
+
)
|
|
97
|
+
from mostlyright.data_harness.sources.catalog.identity_history import (
|
|
98
|
+
IDENTITY_MANIFEST_FILENAME,
|
|
99
|
+
IDENTITY_MANIFEST_SCHEMA,
|
|
100
|
+
)
|
|
101
|
+
from mostlyright.data_harness.sources.catalog.packed_catalog import (
|
|
102
|
+
COUNTER_MEMBERS,
|
|
103
|
+
MAX_RANGE_DESCRIPTORS,
|
|
104
|
+
PACKED_HEAD_FILENAME,
|
|
105
|
+
RANGE_ENTRIES,
|
|
106
|
+
verify_packed_generation,
|
|
107
|
+
)
|
|
108
|
+
from mostlyright.data_harness.sources.catalog.packed_writer import (
|
|
109
|
+
MAX_PACKED_JOURNAL_BYTES,
|
|
110
|
+
PACKED_JOURNAL_FILENAME,
|
|
111
|
+
PACKED_JOURNAL_SCHEMA,
|
|
112
|
+
PackedLimits,
|
|
113
|
+
admit_authored_generation,
|
|
114
|
+
publish_packed_generation,
|
|
115
|
+
verify_packed_receipt,
|
|
116
|
+
)
|
|
117
|
+
from mostlyright.data_harness.sources.catalog.streaming_delta import (
|
|
118
|
+
DELTA_CLASSIFICATIONS,
|
|
119
|
+
DELTA_IDENTITY_DIRNAME,
|
|
120
|
+
DELTA_MANIFEST_FILENAME,
|
|
121
|
+
MAX_DELTA_MANIFEST_BYTES,
|
|
122
|
+
StreamingDeltaLimits,
|
|
123
|
+
SweepMeasures,
|
|
124
|
+
run_streaming_delta,
|
|
125
|
+
)
|
|
126
|
+
from mostlyright.data_harness.sources.contracts import SourceContractError
|
|
127
|
+
from mostlyright.data_harness.ux.commands import CommandSpec
|
|
128
|
+
|
|
129
|
+
PUBLISH_INCOMPLETE_STATUS = "generation_incomplete"
|
|
130
|
+
PUBLISH_COMPLETE_STATUS = "generation_published"
|
|
131
|
+
|
|
132
|
+
#: The refusals that mean *stopped*, not *wrong*: a publication that reached a bound its caller
|
|
133
|
+
#: stated leaves every completed range durable and a typed-incomplete work journal beside them.
|
|
134
|
+
#: Anything else is a defect in the generation and is raised.
|
|
135
|
+
PUBLICATION_INTERRUPTIONS = frozenset({"PACKED_DISK_LIMIT", "PACKED_WALL_LIMIT"})
|
|
136
|
+
|
|
137
|
+
#: What an operator's interruption is recorded as. A budget records the bound it reached instead.
|
|
138
|
+
PUBLISH_INTERRUPTED_REASON = "PUBLISH_INTERRUPTED"
|
|
139
|
+
|
|
140
|
+
#: The two backend coordinates this command admits, spelled the way the closed argv spells them.
|
|
141
|
+
LEXICAL_BACKENDS = ("lexical-hash-v1",)
|
|
142
|
+
NEURAL_BACKENDS = ("minilm-v1",)
|
|
143
|
+
|
|
144
|
+
#: Directories this command owns inside its own output root.
|
|
145
|
+
DELTA_DIRNAME = "delta"
|
|
146
|
+
|
|
147
|
+
#: Every predecessor coordinate, and the mode that requires all six of them.
|
|
148
|
+
_PREDECESSOR_ARGUMENTS = (
|
|
149
|
+
"predecessor_identity_root",
|
|
150
|
+
"predecessor_identity_digest",
|
|
151
|
+
"predecessor_history_root",
|
|
152
|
+
"predecessor_history_digest",
|
|
153
|
+
"predecessor_catalog_root",
|
|
154
|
+
"predecessor_catalog_digest",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
#: The fixed shard/range shape this command may state but never widen.
|
|
158
|
+
_FIXED = {
|
|
159
|
+
"max_ranges": MAX_RANGE_DESCRIPTORS,
|
|
160
|
+
"max_range_entries": RANGE_ENTRIES,
|
|
161
|
+
"max_facts_bytes": 8_388_608,
|
|
162
|
+
"max_member_bytes": 8_388_608,
|
|
163
|
+
"max_posting_segments": 8,
|
|
164
|
+
"max_open_files": 16,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_AUTHOR_RECEIPT_MEMBERS = frozenset(
|
|
168
|
+
{
|
|
169
|
+
"schema_version",
|
|
170
|
+
"status",
|
|
171
|
+
"reason_code",
|
|
172
|
+
"policy_id",
|
|
173
|
+
"policy_sha256",
|
|
174
|
+
"provider_id",
|
|
175
|
+
"harvester_coordinate",
|
|
176
|
+
"staging",
|
|
177
|
+
"coverage",
|
|
178
|
+
"counts",
|
|
179
|
+
"layer_text_digests",
|
|
180
|
+
"shard_count",
|
|
181
|
+
"shards",
|
|
182
|
+
"limits",
|
|
183
|
+
"manifest_sha256",
|
|
184
|
+
"journal_sha256",
|
|
185
|
+
"output_bytes",
|
|
186
|
+
"harvest_receipt_sha256",
|
|
187
|
+
"output_root_sha256",
|
|
188
|
+
"execution",
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
_AUTHOR_MANIFEST_MEMBERS = frozenset(
|
|
192
|
+
{
|
|
193
|
+
"schema_version",
|
|
194
|
+
"policy_id",
|
|
195
|
+
"policy_sha256",
|
|
196
|
+
"staging",
|
|
197
|
+
"coverage",
|
|
198
|
+
"counts",
|
|
199
|
+
"layer_text_digests",
|
|
200
|
+
"shards",
|
|
201
|
+
"root_sha256",
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
_AUTHOR_STAGING_MEMBERS = frozenset(
|
|
205
|
+
{
|
|
206
|
+
"checkpoint_sha256",
|
|
207
|
+
"config_sha256",
|
|
208
|
+
"endpoint",
|
|
209
|
+
"harvester_coordinate",
|
|
210
|
+
"pass_number",
|
|
211
|
+
"pass_map_sha256",
|
|
212
|
+
"pass_map_root_sha256",
|
|
213
|
+
"unique_records",
|
|
214
|
+
"raw_response_index_root_sha256",
|
|
215
|
+
"raw_response_index_count",
|
|
216
|
+
"normalized_page_index_root_sha256",
|
|
217
|
+
"normalized_page_index_count",
|
|
218
|
+
"record_index_root_sha256",
|
|
219
|
+
"record_index_count",
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
_AUTHOR_COUNT_MEMBERS = frozenset(
|
|
223
|
+
{"records", "authored", "flagged", "skipped", "failed", "layer_text_truncated"}
|
|
224
|
+
)
|
|
225
|
+
_AUTHOR_LIMIT_MEMBERS = frozenset(
|
|
226
|
+
{
|
|
227
|
+
"max_records",
|
|
228
|
+
"max_input_bytes",
|
|
229
|
+
"max_shard_entries",
|
|
230
|
+
"max_shard_bytes",
|
|
231
|
+
"max_disk_bytes",
|
|
232
|
+
"max_wall_seconds",
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
_AUTHOR_JOURNAL_MEMBERS = frozenset(
|
|
236
|
+
{
|
|
237
|
+
"schema_version",
|
|
238
|
+
"policy_sha256",
|
|
239
|
+
"staging_checkpoint_sha256",
|
|
240
|
+
"coverage",
|
|
241
|
+
"shards",
|
|
242
|
+
"counts",
|
|
243
|
+
"layer_text_digests",
|
|
244
|
+
"last_provider_record_id",
|
|
245
|
+
"reason_code",
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
#: Exactly how far this command got with each author-receipt member that is *not* a manifest
|
|
250
|
+
#: summary. Every one of these used to be accepted on its own shape, and reconciling them is
|
|
251
|
+
#: what this command now does; but three of them can only be bounded and three cannot be reached
|
|
252
|
+
#: at all, and a composite that named only the unreachable ones would tell a reader the bounded
|
|
253
|
+
#: ones had been established. So the composite states the standing of each, in these four terms:
|
|
254
|
+
#:
|
|
255
|
+
#: ``bound`` re-derived exactly from evidence retained in the author root.
|
|
256
|
+
#: ``bounded_interval`` reconciled against an interval the retained outputs allow rather than an
|
|
257
|
+
#: exact value, because the member measures one invocation of a stage that
|
|
258
|
+
#: may have run more than once.
|
|
259
|
+
#: ``bounded_below`` the authored generation proves the bound was at least this wide. Nothing
|
|
260
|
+
#: retained can show it was not stated wider than the invocation ran under.
|
|
261
|
+
#: ``unverified`` bounds work rather than output. No evidence in the author root reaches
|
|
262
|
+
#: it, and this command does not pretend otherwise.
|
|
263
|
+
AUTHOR_CLAIM_RECONCILIATION = {
|
|
264
|
+
"journal_sha256": "bound",
|
|
265
|
+
"limits.max_disk_bytes": "unverified",
|
|
266
|
+
"limits.max_input_bytes": "unverified",
|
|
267
|
+
"limits.max_records": "bounded_below",
|
|
268
|
+
"limits.max_shard_bytes": "bounded_below",
|
|
269
|
+
"limits.max_shard_entries": "bounded_below",
|
|
270
|
+
"limits.max_wall_seconds": "unverified",
|
|
271
|
+
"output_bytes": "bounded_interval",
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def add_arguments(parser: argparse.ArgumentParser) -> None:
|
|
276
|
+
parser.add_argument(
|
|
277
|
+
"--author-root", type=Path, required=True, help="the authored generation's shard root"
|
|
278
|
+
)
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
"--author-receipt", type=Path, required=True, help="the exact authoring stage receipt"
|
|
281
|
+
)
|
|
282
|
+
parser.add_argument(
|
|
283
|
+
"--author-receipt-sha256", required=True, help="the exact authoring receipt fingerprint"
|
|
284
|
+
)
|
|
285
|
+
parser.add_argument(
|
|
286
|
+
"--harvest-receipt", type=Path, required=True, help="the exact harvest stage receipt"
|
|
287
|
+
)
|
|
288
|
+
parser.add_argument(
|
|
289
|
+
"--harvest-receipt-sha256", required=True, help="the exact harvest receipt fingerprint"
|
|
290
|
+
)
|
|
291
|
+
parser.add_argument(
|
|
292
|
+
"--output-root", type=Path, required=True, help="the packed catalog output root"
|
|
293
|
+
)
|
|
294
|
+
parser.add_argument(
|
|
295
|
+
"--receipt-file",
|
|
296
|
+
type=Path,
|
|
297
|
+
required=True,
|
|
298
|
+
help="where this generation's one composite receipt is installed",
|
|
299
|
+
)
|
|
300
|
+
parser.add_argument(
|
|
301
|
+
"--execution-manifest",
|
|
302
|
+
type=Path,
|
|
303
|
+
required=True,
|
|
304
|
+
help="the committed execution coordinate document",
|
|
305
|
+
)
|
|
306
|
+
parser.add_argument(
|
|
307
|
+
"--execution-commit", required=True, help="the exact commit that coordinate was cut at"
|
|
308
|
+
)
|
|
309
|
+
parser.add_argument(
|
|
310
|
+
"--predecessor-mode",
|
|
311
|
+
choices=("none", "exact"),
|
|
312
|
+
required=True,
|
|
313
|
+
help="whether this generation has an exact predecessor, or none at all",
|
|
314
|
+
)
|
|
315
|
+
parser.add_argument(
|
|
316
|
+
"--predecessor-identity-root", type=Path, help="the predecessor identity generation root"
|
|
317
|
+
)
|
|
318
|
+
parser.add_argument(
|
|
319
|
+
"--predecessor-identity-digest", help="the exact predecessor identity manifest fingerprint"
|
|
320
|
+
)
|
|
321
|
+
parser.add_argument(
|
|
322
|
+
"--predecessor-history-root", type=Path, help="the predecessor classification root"
|
|
323
|
+
)
|
|
324
|
+
parser.add_argument(
|
|
325
|
+
"--predecessor-history-digest", help="the exact predecessor delta manifest fingerprint"
|
|
326
|
+
)
|
|
327
|
+
parser.add_argument(
|
|
328
|
+
"--predecessor-catalog-root", type=Path, help="the predecessor packed catalog root"
|
|
329
|
+
)
|
|
330
|
+
parser.add_argument(
|
|
331
|
+
"--predecessor-catalog-digest", help="the exact predecessor packed head fingerprint"
|
|
332
|
+
)
|
|
333
|
+
parser.add_argument(
|
|
334
|
+
"--lexical-backend",
|
|
335
|
+
choices=LEXICAL_BACKENDS,
|
|
336
|
+
required=True,
|
|
337
|
+
help="the closed lexical representation this generation publishes",
|
|
338
|
+
)
|
|
339
|
+
parser.add_argument(
|
|
340
|
+
"--neural-backend",
|
|
341
|
+
choices=NEURAL_BACKENDS,
|
|
342
|
+
help="the closed neural representation this generation also publishes",
|
|
343
|
+
)
|
|
344
|
+
parser.add_argument("--neural-pin", help="the exact pinned model fingerprint")
|
|
345
|
+
parser.add_argument("--model-root", type=Path, help="the governed local model root")
|
|
346
|
+
for name, ceiling in _FIXED.items():
|
|
347
|
+
parser.add_argument(
|
|
348
|
+
f"--{name.replace('_', '-')}",
|
|
349
|
+
type=int,
|
|
350
|
+
required=True,
|
|
351
|
+
help=f"the fixed packed bound, which is exactly {ceiling}",
|
|
352
|
+
)
|
|
353
|
+
parser.add_argument(
|
|
354
|
+
"--max-batch-invocations",
|
|
355
|
+
type=int,
|
|
356
|
+
required=True,
|
|
357
|
+
help="maximum batch encode calls across every backend",
|
|
358
|
+
)
|
|
359
|
+
parser.add_argument(
|
|
360
|
+
"--max-encoded-layer-items",
|
|
361
|
+
type=int,
|
|
362
|
+
required=True,
|
|
363
|
+
help="maximum encoded layer texts across every backend",
|
|
364
|
+
)
|
|
365
|
+
parser.add_argument(
|
|
366
|
+
"--max-backend-scalar-invocations",
|
|
367
|
+
type=int,
|
|
368
|
+
required=True,
|
|
369
|
+
help="maximum backend scalar calls across every backend",
|
|
370
|
+
)
|
|
371
|
+
parser.add_argument(
|
|
372
|
+
"--max-encoded-input-bytes",
|
|
373
|
+
type=int,
|
|
374
|
+
required=True,
|
|
375
|
+
help="maximum encoded UTF-8 input bytes across every backend",
|
|
376
|
+
)
|
|
377
|
+
parser.add_argument(
|
|
378
|
+
"--max-disk-bytes", type=int, required=True, help="maximum local working disk bytes"
|
|
379
|
+
)
|
|
380
|
+
parser.add_argument(
|
|
381
|
+
"--max-wall-seconds", type=int, required=True, help="maximum cumulative publication seconds"
|
|
382
|
+
)
|
|
383
|
+
parser.add_argument(
|
|
384
|
+
"--resume",
|
|
385
|
+
action="store_true",
|
|
386
|
+
help="continue the publication this receipt file was left incomplete at",
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def run(args: argparse.Namespace) -> dict[str, Any]:
|
|
391
|
+
_validate_limits(args)
|
|
392
|
+
predecessor = _predecessor_coordinates(args)
|
|
393
|
+
execution = load_execution_coordinate(args.execution_manifest, args.execution_commit)
|
|
394
|
+
output_root = Path(args.output_root)
|
|
395
|
+
harvest_raw, harvest = load_stage_receipt(
|
|
396
|
+
args.harvest_receipt,
|
|
397
|
+
args.harvest_receipt_sha256,
|
|
398
|
+
label="harvest_receipt",
|
|
399
|
+
schemas=frozenset({HARVEST_RECEIPT_SCHEMA}),
|
|
400
|
+
)
|
|
401
|
+
author_raw, author = load_stage_receipt(
|
|
402
|
+
args.author_receipt,
|
|
403
|
+
args.author_receipt_sha256,
|
|
404
|
+
label="author_receipt",
|
|
405
|
+
schemas=frozenset({AUTHORING_RECEIPT_SCHEMA}),
|
|
406
|
+
)
|
|
407
|
+
_verify_handoff(
|
|
408
|
+
harvest,
|
|
409
|
+
author,
|
|
410
|
+
harvest_sha256=sha256_bytes(harvest_raw),
|
|
411
|
+
execution=execution,
|
|
412
|
+
)
|
|
413
|
+
manifest_raw, manifest = _author_manifest(Path(args.author_root), author["manifest_sha256"])
|
|
414
|
+
author_evidence = _verify_author_receipt(
|
|
415
|
+
author,
|
|
416
|
+
manifest=manifest,
|
|
417
|
+
manifest_bytes=len(manifest_raw),
|
|
418
|
+
harvest=harvest,
|
|
419
|
+
harvest_sha256=sha256_bytes(harvest_raw),
|
|
420
|
+
author_root=Path(args.author_root),
|
|
421
|
+
execution=execution,
|
|
422
|
+
)
|
|
423
|
+
admit_authored_generation(Path(args.author_root), manifest)
|
|
424
|
+
predecessor_identity_context = (
|
|
425
|
+
contextlib.nullcontext(None)
|
|
426
|
+
if predecessor is None
|
|
427
|
+
else retained_input_directory(
|
|
428
|
+
predecessor["identity_root"],
|
|
429
|
+
label="predecessor_identity_root.manifest",
|
|
430
|
+
code_prefix="GENERATION",
|
|
431
|
+
refusal=SourceContractError,
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
with predecessor_identity_context as predecessor_identity_descriptor:
|
|
435
|
+
predecessor_heads = _read_predecessor_heads(
|
|
436
|
+
predecessor, identity_descriptor=predecessor_identity_descriptor
|
|
437
|
+
)
|
|
438
|
+
with publication_output_lock(
|
|
439
|
+
output_root,
|
|
440
|
+
label="output_root",
|
|
441
|
+
code_prefix="PUBLISH",
|
|
442
|
+
refusal=SourceContractError,
|
|
443
|
+
) as output_descriptor:
|
|
444
|
+
with receipt_installation_lock(
|
|
445
|
+
args.receipt_file,
|
|
446
|
+
label="receipt_file",
|
|
447
|
+
code_prefix="PUBLISH",
|
|
448
|
+
refusal=SourceContractError,
|
|
449
|
+
) as receipt_installation:
|
|
450
|
+
return _run_locked(
|
|
451
|
+
args,
|
|
452
|
+
output_descriptor=output_descriptor,
|
|
453
|
+
receipt_installation=receipt_installation,
|
|
454
|
+
predecessor=predecessor,
|
|
455
|
+
predecessor_identity_descriptor=predecessor_identity_descriptor,
|
|
456
|
+
predecessor_heads=predecessor_heads,
|
|
457
|
+
execution=execution,
|
|
458
|
+
output_root=output_root,
|
|
459
|
+
harvest_raw=harvest_raw,
|
|
460
|
+
harvest=harvest,
|
|
461
|
+
author_raw=author_raw,
|
|
462
|
+
author=author,
|
|
463
|
+
manifest=manifest,
|
|
464
|
+
author_evidence=author_evidence,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _run_locked(
|
|
469
|
+
args: argparse.Namespace,
|
|
470
|
+
*,
|
|
471
|
+
output_descriptor: int,
|
|
472
|
+
receipt_installation: ReceiptInstallation,
|
|
473
|
+
predecessor: dict[str, Any] | None,
|
|
474
|
+
predecessor_identity_descriptor: int | None,
|
|
475
|
+
predecessor_heads: dict[str, Any] | None,
|
|
476
|
+
execution: dict[str, Any],
|
|
477
|
+
output_root: Path,
|
|
478
|
+
harvest_raw: bytes,
|
|
479
|
+
harvest: dict[str, Any],
|
|
480
|
+
author_raw: bytes,
|
|
481
|
+
author: dict[str, Any],
|
|
482
|
+
manifest: dict[str, Any],
|
|
483
|
+
author_evidence: dict[str, Any],
|
|
484
|
+
) -> dict[str, Any]:
|
|
485
|
+
retained = None
|
|
486
|
+
orphaned_journal = False
|
|
487
|
+
terminal_publication = False
|
|
488
|
+
if args.resume:
|
|
489
|
+
retained = retained_incomplete_receipt(
|
|
490
|
+
args.receipt_file,
|
|
491
|
+
maximum=MAX_COMPOSITE_RECEIPT_BYTES,
|
|
492
|
+
label="receipt_file",
|
|
493
|
+
schema=GENERATION_RECEIPT_SCHEMA,
|
|
494
|
+
incomplete_status=PUBLISH_INCOMPLETE_STATUS,
|
|
495
|
+
output_root=output_root,
|
|
496
|
+
code_prefix="PUBLISH",
|
|
497
|
+
refusal=SourceContractError,
|
|
498
|
+
)
|
|
499
|
+
if retained is None:
|
|
500
|
+
try:
|
|
501
|
+
os.stat(PACKED_HEAD_FILENAME, dir_fd=output_descriptor, follow_symlinks=False)
|
|
502
|
+
terminal_publication = True
|
|
503
|
+
except FileNotFoundError:
|
|
504
|
+
terminal_publication = False
|
|
505
|
+
if (
|
|
506
|
+
_retained_journal(output_descriptor, logical_output_root=output_root) is None
|
|
507
|
+
and not terminal_publication
|
|
508
|
+
):
|
|
509
|
+
raise SourceContractError(
|
|
510
|
+
"PUBLISH_RESUME_RECEIPT",
|
|
511
|
+
"receipt_file",
|
|
512
|
+
"a resume continues the typed-incomplete receipt its interruption left behind, "
|
|
513
|
+
"or the work journal a publication was killed in the middle of",
|
|
514
|
+
)
|
|
515
|
+
orphaned_journal = not terminal_publication
|
|
516
|
+
else:
|
|
517
|
+
_require_retained_journal(retained, output_descriptor, logical_output_root=output_root)
|
|
518
|
+
elif Path(args.receipt_file).exists():
|
|
519
|
+
raise SourceContractError(
|
|
520
|
+
"PUBLISH_RECEIPT_DESTINATION",
|
|
521
|
+
"receipt_file",
|
|
522
|
+
"receipt destination must be an absent no-follow path",
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
require_output_root_identity(
|
|
526
|
+
output_root,
|
|
527
|
+
output_descriptor,
|
|
528
|
+
label="output_root",
|
|
529
|
+
code_prefix="PUBLISH",
|
|
530
|
+
refusal=SourceContractError,
|
|
531
|
+
)
|
|
532
|
+
delta_root = output_root / DELTA_DIRNAME
|
|
533
|
+
logical_delta_root = output_root / DELTA_DIRNAME
|
|
534
|
+
classification = _classify(
|
|
535
|
+
args,
|
|
536
|
+
author=author,
|
|
537
|
+
harvest=harvest,
|
|
538
|
+
delta_root=delta_root,
|
|
539
|
+
output_descriptor=output_descriptor,
|
|
540
|
+
coordinate_root=logical_delta_root,
|
|
541
|
+
predecessor=predecessor,
|
|
542
|
+
predecessor_identity_descriptor=predecessor_identity_descriptor,
|
|
543
|
+
resumed=args.resume,
|
|
544
|
+
)
|
|
545
|
+
backends = _backends(args)
|
|
546
|
+
# Every block that describes this generation rather than this invocation of it. Both receipts
|
|
547
|
+
# this command can write state exactly these, so an interruption cannot describe its inputs
|
|
548
|
+
# differently from the publication that completes them.
|
|
549
|
+
common: dict[str, Any] = {
|
|
550
|
+
"components": {
|
|
551
|
+
"harvest_receipt": harvest,
|
|
552
|
+
"harvest_receipt_sha256": sha256_bytes(harvest_raw),
|
|
553
|
+
"author_receipt": author,
|
|
554
|
+
"author_receipt_sha256": sha256_bytes(author_raw),
|
|
555
|
+
},
|
|
556
|
+
"execution": execution,
|
|
557
|
+
"staging": author["staging"],
|
|
558
|
+
"policy": {
|
|
559
|
+
"policy_id": author["policy_id"],
|
|
560
|
+
"policy_sha256": author["policy_sha256"],
|
|
561
|
+
"provider_id": author["provider_id"],
|
|
562
|
+
"harvester_coordinate": author["harvester_coordinate"],
|
|
563
|
+
},
|
|
564
|
+
"author": {
|
|
565
|
+
"manifest_sha256": author["manifest_sha256"],
|
|
566
|
+
"root_sha256": manifest["root_sha256"],
|
|
567
|
+
"shard_count": len(manifest["shards"]),
|
|
568
|
+
# What this generation covers, stated in the composite's own summary as well as
|
|
569
|
+
# carried in the author receipt beside it. A reader of the authoritative receipt
|
|
570
|
+
# answers "is this the whole sweep" without opening a component document.
|
|
571
|
+
"coverage": manifest["coverage"],
|
|
572
|
+
"counts": manifest["counts"],
|
|
573
|
+
"layer_text_digests": manifest["layer_text_digests"],
|
|
574
|
+
**author_evidence,
|
|
575
|
+
},
|
|
576
|
+
"classification": classification,
|
|
577
|
+
"predecessor": predecessor_heads,
|
|
578
|
+
"limits": _limits_evidence(args),
|
|
579
|
+
}
|
|
580
|
+
if retained is not None:
|
|
581
|
+
_require_retained_generation(retained, common)
|
|
582
|
+
publication_binding_sha256 = _generation_binding_sha256(common)
|
|
583
|
+
if orphaned_journal:
|
|
584
|
+
_adopt_orphaned_journal(
|
|
585
|
+
author=author,
|
|
586
|
+
predecessor=predecessor,
|
|
587
|
+
output_root=output_root,
|
|
588
|
+
output_descriptor=output_descriptor,
|
|
589
|
+
publication_binding_sha256=publication_binding_sha256,
|
|
590
|
+
)
|
|
591
|
+
try:
|
|
592
|
+
with retained_child_directory(
|
|
593
|
+
output_descriptor,
|
|
594
|
+
DELTA_DIRNAME,
|
|
595
|
+
create=False,
|
|
596
|
+
label="delta.output",
|
|
597
|
+
code_prefix="PUBLISH",
|
|
598
|
+
refusal=SourceContractError,
|
|
599
|
+
) as delta_descriptor:
|
|
600
|
+
publication = publish_packed_generation(
|
|
601
|
+
delta_root=delta_root,
|
|
602
|
+
delta_descriptor=delta_descriptor,
|
|
603
|
+
expected_delta_manifest_sha256=classification["manifest_sha256"],
|
|
604
|
+
authoring_root=Path(args.author_root),
|
|
605
|
+
expected_authoring_manifest_sha256=author["manifest_sha256"],
|
|
606
|
+
output_root=output_root,
|
|
607
|
+
output_descriptor=output_descriptor,
|
|
608
|
+
backends=backends,
|
|
609
|
+
predecessor_root=None if predecessor is None else predecessor["catalog_root"],
|
|
610
|
+
expected_predecessor_head_sha256=(
|
|
611
|
+
None if predecessor is None else predecessor["catalog_digest"]
|
|
612
|
+
),
|
|
613
|
+
expected_predecessor_generation_sha256=(
|
|
614
|
+
None if predecessor is None else predecessor["history_digest"]
|
|
615
|
+
),
|
|
616
|
+
limits=PackedLimits(
|
|
617
|
+
max_ranges=args.max_ranges,
|
|
618
|
+
max_entries=args.max_ranges * args.max_range_entries,
|
|
619
|
+
max_disk_bytes=args.max_disk_bytes,
|
|
620
|
+
max_wall_seconds=args.max_wall_seconds,
|
|
621
|
+
),
|
|
622
|
+
resume=args.resume,
|
|
623
|
+
publication_binding_sha256=publication_binding_sha256,
|
|
624
|
+
)
|
|
625
|
+
except SourceContractError as refusal:
|
|
626
|
+
if refusal.code not in PUBLICATION_INTERRUPTIONS:
|
|
627
|
+
raise
|
|
628
|
+
require_output_root_identity(
|
|
629
|
+
output_root,
|
|
630
|
+
output_descriptor,
|
|
631
|
+
label="output_root",
|
|
632
|
+
code_prefix="PUBLISH",
|
|
633
|
+
refusal=SourceContractError,
|
|
634
|
+
)
|
|
635
|
+
stopped = _incomplete_receipt(
|
|
636
|
+
args,
|
|
637
|
+
common=common,
|
|
638
|
+
output_root=output_root,
|
|
639
|
+
output_descriptor=output_descriptor,
|
|
640
|
+
reason_code=refusal.code,
|
|
641
|
+
retained=retained,
|
|
642
|
+
receipt_installation=receipt_installation,
|
|
643
|
+
)
|
|
644
|
+
if stopped is None:
|
|
645
|
+
raise
|
|
646
|
+
return stopped
|
|
647
|
+
except KeyboardInterrupt:
|
|
648
|
+
require_output_root_identity(
|
|
649
|
+
output_root,
|
|
650
|
+
output_descriptor,
|
|
651
|
+
label="output_root",
|
|
652
|
+
code_prefix="PUBLISH",
|
|
653
|
+
refusal=SourceContractError,
|
|
654
|
+
)
|
|
655
|
+
stopped = _incomplete_receipt(
|
|
656
|
+
args,
|
|
657
|
+
common=common,
|
|
658
|
+
output_root=output_root,
|
|
659
|
+
output_descriptor=output_descriptor,
|
|
660
|
+
reason_code=PUBLISH_INTERRUPTED_REASON,
|
|
661
|
+
retained=retained,
|
|
662
|
+
receipt_installation=receipt_installation,
|
|
663
|
+
)
|
|
664
|
+
if stopped is None:
|
|
665
|
+
raise
|
|
666
|
+
return stopped
|
|
667
|
+
receipt = publication.receipt
|
|
668
|
+
_verify_work_budget(args, receipt)
|
|
669
|
+
verify_packed_receipt(receipt, root=output_root, root_descriptor=output_descriptor)
|
|
670
|
+
installed = verify_packed_generation(
|
|
671
|
+
output_root,
|
|
672
|
+
expected_head_sha256=publication.head_sha256,
|
|
673
|
+
root_descriptor=output_descriptor,
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
payload = compose_generation_receipt(
|
|
677
|
+
{
|
|
678
|
+
"schema_version": GENERATION_RECEIPT_SCHEMA,
|
|
679
|
+
"status": PUBLISH_COMPLETE_STATUS,
|
|
680
|
+
"reason_code": None,
|
|
681
|
+
"published_at": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
682
|
+
"output_root_sha256": root_coordinate(output_root),
|
|
683
|
+
**common,
|
|
684
|
+
"publication": _publication_evidence(receipt, publication, backends),
|
|
685
|
+
"readback": {
|
|
686
|
+
"reader": "packed_catalog.verify_packed_generation",
|
|
687
|
+
"head_sha256": installed.head_sha256,
|
|
688
|
+
"range_count": installed.range_count,
|
|
689
|
+
"entry_count": installed.entry_count,
|
|
690
|
+
"member_count": installed.member_count,
|
|
691
|
+
"posting_term_count": installed.posting_term_count,
|
|
692
|
+
"backends": list(installed.backends),
|
|
693
|
+
"readback_sha256": canonical_sha256(
|
|
694
|
+
{
|
|
695
|
+
"backends": list(installed.backends),
|
|
696
|
+
"entry_count": installed.entry_count,
|
|
697
|
+
"head_sha256": installed.head_sha256,
|
|
698
|
+
"member_count": installed.member_count,
|
|
699
|
+
"posting_term_count": installed.posting_term_count,
|
|
700
|
+
"range_count": installed.range_count,
|
|
701
|
+
}
|
|
702
|
+
),
|
|
703
|
+
},
|
|
704
|
+
}
|
|
705
|
+
)
|
|
706
|
+
require_output_root_identity(
|
|
707
|
+
output_root,
|
|
708
|
+
output_descriptor,
|
|
709
|
+
label="output_root",
|
|
710
|
+
code_prefix="PUBLISH",
|
|
711
|
+
refusal=SourceContractError,
|
|
712
|
+
)
|
|
713
|
+
install_receipt(
|
|
714
|
+
args.receipt_file,
|
|
715
|
+
payload,
|
|
716
|
+
maximum=MAX_COMPOSITE_RECEIPT_BYTES,
|
|
717
|
+
label="receipt_file",
|
|
718
|
+
code_prefix="PUBLISH",
|
|
719
|
+
refusal=SourceContractError,
|
|
720
|
+
replaces=retained,
|
|
721
|
+
installation=receipt_installation,
|
|
722
|
+
commit_guard=lambda: require_output_root_identity(
|
|
723
|
+
output_root,
|
|
724
|
+
output_descriptor,
|
|
725
|
+
label="output_root",
|
|
726
|
+
code_prefix="PUBLISH",
|
|
727
|
+
refusal=SourceContractError,
|
|
728
|
+
),
|
|
729
|
+
)
|
|
730
|
+
return payload
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
# ------------------------------------------------------------------------------------------------
|
|
734
|
+
# Stopping: the receipt an interruption leaves, and the resume that continues exactly it
|
|
735
|
+
# ------------------------------------------------------------------------------------------------
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def _retained_journal(
|
|
739
|
+
output_descriptor: int, *, logical_output_root: Path
|
|
740
|
+
) -> tuple[bytes, dict[str, Any]] | None:
|
|
741
|
+
"""The typed-incomplete work journal an interruption left in this root, or nothing at all.
|
|
742
|
+
|
|
743
|
+
A journal that is absent, that is complete, or that was written for another root is not
|
|
744
|
+
something to continue: each of those is reported as *no retained publication* rather than
|
|
745
|
+
guessed at, and the caller decides what that means where it stands.
|
|
746
|
+
"""
|
|
747
|
+
|
|
748
|
+
try:
|
|
749
|
+
raw, journal = load_bounded_json_at(
|
|
750
|
+
output_descriptor,
|
|
751
|
+
PACKED_JOURNAL_FILENAME,
|
|
752
|
+
label="packed.journal",
|
|
753
|
+
maximum=MAX_PACKED_JOURNAL_BYTES,
|
|
754
|
+
max_depth=MAX_PREDECESSOR_HEAD_DEPTH,
|
|
755
|
+
max_members=MAX_STAGE_RECEIPT_MEMBERS,
|
|
756
|
+
max_items=MAX_COMPOSITE_ARRAY_ITEMS,
|
|
757
|
+
)
|
|
758
|
+
except SourceContractError as error:
|
|
759
|
+
if error.code == "GENERATION_INPUT_PATH":
|
|
760
|
+
try:
|
|
761
|
+
os.stat(PACKED_JOURNAL_FILENAME, dir_fd=output_descriptor, follow_symlinks=False)
|
|
762
|
+
except FileNotFoundError:
|
|
763
|
+
return None
|
|
764
|
+
raise
|
|
765
|
+
if (
|
|
766
|
+
journal.get("schema_version") != PACKED_JOURNAL_SCHEMA
|
|
767
|
+
or journal.get("status") != "incomplete"
|
|
768
|
+
or journal.get("head_sha256") is not None
|
|
769
|
+
or journal.get("output_root") != os.path.abspath(os.fspath(logical_output_root))
|
|
770
|
+
):
|
|
771
|
+
return None
|
|
772
|
+
return raw, journal
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def _candidate_journals(output_descriptor: int) -> list[str]:
|
|
776
|
+
"""Every document directly inside this output root that claims to be a packed work journal.
|
|
777
|
+
|
|
778
|
+
A publication writes exactly one, under one name. Anything else here -- a copy somebody kept,
|
|
779
|
+
a journal moved aside by hand -- makes *which publication is being continued* a question, and
|
|
780
|
+
a resume answers questions like that by refusing rather than by picking one.
|
|
781
|
+
"""
|
|
782
|
+
|
|
783
|
+
names: list[str] = []
|
|
784
|
+
for name in sorted(os.listdir(output_descriptor)):
|
|
785
|
+
try:
|
|
786
|
+
_raw, document = load_bounded_json_at(
|
|
787
|
+
output_descriptor,
|
|
788
|
+
name,
|
|
789
|
+
label="packed.journal",
|
|
790
|
+
maximum=MAX_PACKED_JOURNAL_BYTES,
|
|
791
|
+
max_depth=MAX_PREDECESSOR_HEAD_DEPTH,
|
|
792
|
+
max_members=MAX_STAGE_RECEIPT_MEMBERS,
|
|
793
|
+
max_items=MAX_COMPOSITE_ARRAY_ITEMS,
|
|
794
|
+
)
|
|
795
|
+
except SourceContractError:
|
|
796
|
+
# Anything that is not a bounded canonical document is not a work journal. Directories,
|
|
797
|
+
# packs, heads and receipts all land here, which is why this reads rather than lists.
|
|
798
|
+
continue
|
|
799
|
+
if document.get("schema_version") == PACKED_JOURNAL_SCHEMA:
|
|
800
|
+
names.append(name)
|
|
801
|
+
return names
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
def _adopt_orphaned_journal(
|
|
805
|
+
*,
|
|
806
|
+
author: Mapping[str, Any],
|
|
807
|
+
predecessor: Mapping[str, Any] | None,
|
|
808
|
+
output_root: Path,
|
|
809
|
+
output_descriptor: int,
|
|
810
|
+
publication_binding_sha256: str,
|
|
811
|
+
) -> None:
|
|
812
|
+
"""Continue the publication a hard kill left behind, which left no receipt to continue.
|
|
813
|
+
|
|
814
|
+
A publication that reaches a bound it was given writes down where it got to. One that is
|
|
815
|
+
killed outright -- power, sleep, the kernel -- writes nothing, because nothing in it ever runs
|
|
816
|
+
again: the only state left is the work journal and the immutable members it names. That state
|
|
817
|
+
is adoptable, and for a multi-hour publication it is the difference between continuing and
|
|
818
|
+
starting again. It is adopted only when it is *this* argv's publication, so the journal has to
|
|
819
|
+
bind:
|
|
820
|
+
|
|
821
|
+
* this exact output root, which the journal states and which ``_retained_journal`` checks;
|
|
822
|
+
* one interrupted publication, and not a choice between several;
|
|
823
|
+
* the exact execution, component receipts, staging, policy, authoring, classification,
|
|
824
|
+
predecessor and immutable limit evidence bound before the first packed range was written;
|
|
825
|
+
* the provider and the exact predecessor head this invocation was given.
|
|
826
|
+
|
|
827
|
+
The rest of the generation coordinate -- backends, posting backend, classification and identity
|
|
828
|
+
manifests -- and the exact bytes of every member each completed range names are re-derived by
|
|
829
|
+
the publisher's own resume law, before it does one byte of new work and before anything here
|
|
830
|
+
is written. That law is not copied into this command; a second copy of it would be a second
|
|
831
|
+
thing to keep true.
|
|
832
|
+
"""
|
|
833
|
+
|
|
834
|
+
candidates = _candidate_journals(output_descriptor)
|
|
835
|
+
if len(candidates) > 1:
|
|
836
|
+
raise SourceContractError(
|
|
837
|
+
"PUBLISH_RESUME_JOURNAL",
|
|
838
|
+
"packed.journal",
|
|
839
|
+
"this output root holds more than one work journal, and a resume continues exactly one",
|
|
840
|
+
)
|
|
841
|
+
found = _retained_journal(output_descriptor, logical_output_root=output_root)
|
|
842
|
+
if found is None:
|
|
843
|
+
raise SourceContractError(
|
|
844
|
+
"PUBLISH_RESUME_RECEIPT",
|
|
845
|
+
"receipt_file",
|
|
846
|
+
"a resume continues the typed-incomplete receipt its interruption left behind, or the "
|
|
847
|
+
"work journal a publication was killed in the middle of",
|
|
848
|
+
)
|
|
849
|
+
generation = found[1].get("generation")
|
|
850
|
+
stated = {
|
|
851
|
+
"authoring_manifest_sha256": author["manifest_sha256"],
|
|
852
|
+
"publication_binding_sha256": publication_binding_sha256,
|
|
853
|
+
"provider_id": author["provider_id"],
|
|
854
|
+
"predecessor_head_sha256": None if predecessor is None else predecessor["catalog_digest"],
|
|
855
|
+
}
|
|
856
|
+
if not isinstance(generation, Mapping) or any(
|
|
857
|
+
generation.get(member) != value for member, value in stated.items()
|
|
858
|
+
):
|
|
859
|
+
raise SourceContractError(
|
|
860
|
+
"PUBLISH_RESUME_JOURNAL",
|
|
861
|
+
"packed.journal",
|
|
862
|
+
"the interrupted publication in this output root is of another generation than the "
|
|
863
|
+
"one this invocation was given",
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
def _require_retained_journal(
|
|
868
|
+
retained: Mapping[str, Any], output_descriptor: int, *, logical_output_root: Path
|
|
869
|
+
) -> None:
|
|
870
|
+
"""A resume continues the exact interrupted publication its receipt was written against."""
|
|
871
|
+
|
|
872
|
+
stated = retained.get("publication")
|
|
873
|
+
stated_sha256 = stated.get("journal_sha256") if isinstance(stated, Mapping) else None
|
|
874
|
+
found = _retained_journal(output_descriptor, logical_output_root=logical_output_root)
|
|
875
|
+
if found is None or not is_digest(stated_sha256) or sha256_bytes(found[0]) != stated_sha256:
|
|
876
|
+
raise SourceContractError(
|
|
877
|
+
"PUBLISH_RESUME_RECEIPT",
|
|
878
|
+
"packed.journal",
|
|
879
|
+
"a resume continues the exact interrupted publication its receipt was left bound to",
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def _require_retained_generation(retained: Mapping[str, Any], common: Mapping[str, Any]) -> None:
|
|
884
|
+
"""Require every generation-defining block to match the interrupted invocation."""
|
|
885
|
+
|
|
886
|
+
retained_components = retained.get("components")
|
|
887
|
+
current_components = common.get("components")
|
|
888
|
+
if isinstance(retained_components, Mapping) and isinstance(current_components, Mapping):
|
|
889
|
+
for stage in ("author", "harvest"):
|
|
890
|
+
member = f"{stage}_receipt_sha256"
|
|
891
|
+
if retained_components.get(member) != current_components.get(member):
|
|
892
|
+
raise SourceContractError(
|
|
893
|
+
"PUBLISH_RESUME_RECEIPT",
|
|
894
|
+
f"{stage}_receipt",
|
|
895
|
+
"the retained publication was built from another stage receipt",
|
|
896
|
+
)
|
|
897
|
+
for member in (
|
|
898
|
+
"components",
|
|
899
|
+
"execution",
|
|
900
|
+
"staging",
|
|
901
|
+
"policy",
|
|
902
|
+
"author",
|
|
903
|
+
"classification",
|
|
904
|
+
"predecessor",
|
|
905
|
+
):
|
|
906
|
+
if retained.get(member) != common.get(member):
|
|
907
|
+
raise SourceContractError(
|
|
908
|
+
"PUBLISH_RESUME_RECEIPT",
|
|
909
|
+
member,
|
|
910
|
+
"a resume must use the exact generation evidence retained at interruption",
|
|
911
|
+
)
|
|
912
|
+
retained_limits = retained.get("limits")
|
|
913
|
+
current_limits = common.get("limits")
|
|
914
|
+
mutable = {"max_disk_bytes", "max_wall_seconds"}
|
|
915
|
+
if (
|
|
916
|
+
not isinstance(retained_limits, Mapping)
|
|
917
|
+
or not isinstance(current_limits, Mapping)
|
|
918
|
+
or {key: value for key, value in retained_limits.items() if key not in mutable}
|
|
919
|
+
!= {key: value for key, value in current_limits.items() if key not in mutable}
|
|
920
|
+
):
|
|
921
|
+
raise SourceContractError(
|
|
922
|
+
"PUBLISH_RESUME_RECEIPT",
|
|
923
|
+
"limits",
|
|
924
|
+
"a resume may change only its cumulative disk and wall-time ceilings",
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def _generation_binding_sha256(common: Mapping[str, Any]) -> str:
|
|
929
|
+
"""Bind immutable generation evidence into the packed work journal."""
|
|
930
|
+
|
|
931
|
+
limits = common.get("limits")
|
|
932
|
+
if not isinstance(limits, Mapping):
|
|
933
|
+
raise SourceContractError(
|
|
934
|
+
"PUBLISH_LIMIT", "limits", "generation limits must be one exact object"
|
|
935
|
+
)
|
|
936
|
+
mutable = {"max_disk_bytes", "max_wall_seconds"}
|
|
937
|
+
return canonical_sha256(
|
|
938
|
+
{
|
|
939
|
+
**common,
|
|
940
|
+
"limits": {key: value for key, value in limits.items() if key not in mutable},
|
|
941
|
+
}
|
|
942
|
+
)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def _journal_evidence(journal: Mapping[str, Any]) -> dict[str, Any] | None:
|
|
946
|
+
"""What one typed-incomplete journal states about the work it got as far as.
|
|
947
|
+
|
|
948
|
+
Nothing here is believed because the journal says it: a record whose shape is not the one the
|
|
949
|
+
publisher writes makes this report nothing, and the publication's own refusal is raised
|
|
950
|
+
instead of a receipt describing work that cannot be read.
|
|
951
|
+
"""
|
|
952
|
+
|
|
953
|
+
invocations = journal.get("invocations")
|
|
954
|
+
completed = journal.get("completed_ranges")
|
|
955
|
+
generation = journal.get("generation")
|
|
956
|
+
if (
|
|
957
|
+
not isinstance(invocations, list)
|
|
958
|
+
or not invocations
|
|
959
|
+
or not isinstance(completed, list)
|
|
960
|
+
or not isinstance(generation, Mapping)
|
|
961
|
+
):
|
|
962
|
+
return None
|
|
963
|
+
records: list[dict[str, int]] = []
|
|
964
|
+
for record in invocations:
|
|
965
|
+
if not isinstance(record, Mapping) or set(record) != set(COUNTER_MEMBERS):
|
|
966
|
+
return None
|
|
967
|
+
if any(type(record[name]) is not int or record[name] < 0 for name in COUNTER_MEMBERS):
|
|
968
|
+
return None
|
|
969
|
+
records.append({name: record[name] for name in COUNTER_MEMBERS})
|
|
970
|
+
ranges: list[str] = []
|
|
971
|
+
for descriptor in completed:
|
|
972
|
+
if not isinstance(descriptor, Mapping) or not is_digest(
|
|
973
|
+
descriptor.get("range_manifest_sha256")
|
|
974
|
+
):
|
|
975
|
+
return None
|
|
976
|
+
ranges.append(descriptor["range_manifest_sha256"])
|
|
977
|
+
backends = generation.get("backends")
|
|
978
|
+
if not isinstance(backends, list) or not all(isinstance(name, str) for name in backends):
|
|
979
|
+
return None
|
|
980
|
+
return {
|
|
981
|
+
"journal_status": "incomplete",
|
|
982
|
+
"completed_range_count": len(ranges),
|
|
983
|
+
"range_manifest_sha256s": ranges,
|
|
984
|
+
"counter_records": records,
|
|
985
|
+
"counters": {name: sum(record[name] for record in records) for name in COUNTER_MEMBERS},
|
|
986
|
+
"backends": list(backends),
|
|
987
|
+
"predecessor": generation.get("predecessor_head_sha256"),
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def _incomplete_receipt(
|
|
992
|
+
args: argparse.Namespace,
|
|
993
|
+
*,
|
|
994
|
+
common: Mapping[str, Any],
|
|
995
|
+
output_root: Path,
|
|
996
|
+
output_descriptor: int,
|
|
997
|
+
reason_code: str,
|
|
998
|
+
retained: Mapping[str, Any] | None,
|
|
999
|
+
receipt_installation: ReceiptInstallation,
|
|
1000
|
+
) -> dict[str, Any] | None:
|
|
1001
|
+
"""Install the typed-incomplete composite receipt one stopped publication leaves behind.
|
|
1002
|
+
|
|
1003
|
+
A publication that stops between two ranges has left every range before it durable and a
|
|
1004
|
+
typed-incomplete work journal beside them. This binds that journal's exact bytes, this output
|
|
1005
|
+
root, the ranges that are already immutable and the ordered per-invocation counter records the
|
|
1006
|
+
work got as far as -- which is precisely what ``--resume`` validates and replaces.
|
|
1007
|
+
|
|
1008
|
+
A publication that stopped before its first range committed anything has left nothing to
|
|
1009
|
+
continue, and gets no receipt: the refusal that stopped it is raised instead.
|
|
1010
|
+
"""
|
|
1011
|
+
|
|
1012
|
+
found = _retained_journal(output_descriptor, logical_output_root=output_root)
|
|
1013
|
+
if found is None:
|
|
1014
|
+
return None
|
|
1015
|
+
raw, journal = found
|
|
1016
|
+
evidence = _journal_evidence(journal)
|
|
1017
|
+
if evidence is None:
|
|
1018
|
+
return None
|
|
1019
|
+
payload = compose_generation_receipt(
|
|
1020
|
+
{
|
|
1021
|
+
"schema_version": GENERATION_RECEIPT_SCHEMA,
|
|
1022
|
+
"status": PUBLISH_INCOMPLETE_STATUS,
|
|
1023
|
+
"reason_code": reason_code,
|
|
1024
|
+
"published_at": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
1025
|
+
"output_root_sha256": root_coordinate(output_root),
|
|
1026
|
+
**common,
|
|
1027
|
+
"publication": {"journal_sha256": sha256_bytes(raw), **evidence},
|
|
1028
|
+
# A generation that was not published has nothing to read back. The block is stated
|
|
1029
|
+
# rather than dropped, because a composite receipt states every one of its blocks.
|
|
1030
|
+
"readback": None,
|
|
1031
|
+
}
|
|
1032
|
+
)
|
|
1033
|
+
install_receipt(
|
|
1034
|
+
args.receipt_file,
|
|
1035
|
+
payload,
|
|
1036
|
+
maximum=MAX_COMPOSITE_RECEIPT_BYTES,
|
|
1037
|
+
label="receipt_file",
|
|
1038
|
+
code_prefix="PUBLISH",
|
|
1039
|
+
refusal=SourceContractError,
|
|
1040
|
+
replaces=retained,
|
|
1041
|
+
installation=receipt_installation,
|
|
1042
|
+
)
|
|
1043
|
+
return payload
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
# ------------------------------------------------------------------------------------------------
|
|
1047
|
+
# The handoff: two receipts that have to describe one staging checkpoint
|
|
1048
|
+
# ------------------------------------------------------------------------------------------------
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
def _verify_handoff(
|
|
1052
|
+
harvest: Mapping[str, Any],
|
|
1053
|
+
author: Mapping[str, Any],
|
|
1054
|
+
*,
|
|
1055
|
+
harvest_sha256: str,
|
|
1056
|
+
execution: Mapping[str, Any],
|
|
1057
|
+
) -> None:
|
|
1058
|
+
if harvest.get("status") != "harvest_complete" or author.get("status") != "complete":
|
|
1059
|
+
raise SourceContractError(
|
|
1060
|
+
"PUBLISH_UPSTREAM_INCOMPLETE",
|
|
1061
|
+
"components",
|
|
1062
|
+
"a generation is published from a complete sweep and a complete authored generation",
|
|
1063
|
+
)
|
|
1064
|
+
if not is_digest(author.get("manifest_sha256")):
|
|
1065
|
+
raise SourceContractError(
|
|
1066
|
+
"PUBLISH_UPSTREAM_INCOMPLETE",
|
|
1067
|
+
"author_receipt.manifest_sha256",
|
|
1068
|
+
"the authored generation published no manifest to classify",
|
|
1069
|
+
)
|
|
1070
|
+
if author.get("harvest_receipt_sha256") != harvest_sha256:
|
|
1071
|
+
raise SourceContractError(
|
|
1072
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1073
|
+
"author_receipt.harvest_receipt_sha256",
|
|
1074
|
+
"the authoring receipt must bind the exact harvest receipt supplied for publication",
|
|
1075
|
+
)
|
|
1076
|
+
if harvest.get("execution") != execution or author.get("execution") != execution:
|
|
1077
|
+
raise SourceContractError(
|
|
1078
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1079
|
+
"components.execution",
|
|
1080
|
+
"harvest, author, and publisher must use one exact execution coordinate",
|
|
1081
|
+
)
|
|
1082
|
+
staging = author.get("staging")
|
|
1083
|
+
if not isinstance(staging, Mapping):
|
|
1084
|
+
raise SourceContractError(
|
|
1085
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1086
|
+
"author_receipt.staging",
|
|
1087
|
+
"an authoring receipt names the staging coordinate it authored",
|
|
1088
|
+
)
|
|
1089
|
+
for stated, retained, member in (
|
|
1090
|
+
(harvest.get("checkpoint_sha256"), staging.get("checkpoint_sha256"), "checkpoint_sha256"),
|
|
1091
|
+
(harvest.get("config_sha256"), staging.get("config_sha256"), "config_sha256"),
|
|
1092
|
+
(harvest.get("endpoint"), staging.get("endpoint"), "endpoint"),
|
|
1093
|
+
(
|
|
1094
|
+
harvest.get("harvester_coordinate"),
|
|
1095
|
+
staging.get("harvester_coordinate"),
|
|
1096
|
+
"harvester_coordinate",
|
|
1097
|
+
),
|
|
1098
|
+
):
|
|
1099
|
+
if stated is None or stated != retained:
|
|
1100
|
+
raise SourceContractError(
|
|
1101
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1102
|
+
f"author_receipt.staging.{member}",
|
|
1103
|
+
"the two stage receipts describe two different sweeps",
|
|
1104
|
+
)
|
|
1105
|
+
measures = harvest.get("measures")
|
|
1106
|
+
if not isinstance(measures, Mapping) or set(measures) != {
|
|
1107
|
+
"requests",
|
|
1108
|
+
"responses",
|
|
1109
|
+
"pages",
|
|
1110
|
+
"provider_records",
|
|
1111
|
+
}:
|
|
1112
|
+
raise SourceContractError(
|
|
1113
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1114
|
+
"harvest_receipt.measures",
|
|
1115
|
+
"a sweep states requests, responses, pages and provider records as four numbers",
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def _author_manifest(root: Path, expected_sha256: str) -> tuple[bytes, dict[str, Any]]:
|
|
1120
|
+
raw, manifest = load_bounded_json(
|
|
1121
|
+
root / MANIFEST_FILENAME,
|
|
1122
|
+
label="author_root.manifest",
|
|
1123
|
+
maximum=MAX_AUTHORING_MANIFEST_BYTES,
|
|
1124
|
+
max_depth=MAX_PREDECESSOR_HEAD_DEPTH,
|
|
1125
|
+
max_members=MAX_STAGE_RECEIPT_MEMBERS,
|
|
1126
|
+
max_items=MAX_PREDECESSOR_RANGE_DESCRIPTORS,
|
|
1127
|
+
expected_sha256=expected_sha256,
|
|
1128
|
+
)
|
|
1129
|
+
if manifest.get("schema_version") != AUTHORING_MANIFEST_SCHEMA:
|
|
1130
|
+
raise SourceContractError(
|
|
1131
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1132
|
+
"author_root.manifest",
|
|
1133
|
+
"the authored root does not hold an authoring manifest",
|
|
1134
|
+
)
|
|
1135
|
+
return raw, manifest
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
def _author_journal(root: Path, expected_sha256: str) -> tuple[bytes, dict[str, Any]]:
|
|
1139
|
+
"""Read the work journal the authoring stage retained beside its shards, by exact digest.
|
|
1140
|
+
|
|
1141
|
+
The authoring stage publishes ``journal.json`` on every invocation and never removes it, so
|
|
1142
|
+
the digest an author receipt states is a claim about bytes that are still on disk. Reading it
|
|
1143
|
+
here is what turns that claim back into evidence.
|
|
1144
|
+
"""
|
|
1145
|
+
|
|
1146
|
+
return load_bounded_json(
|
|
1147
|
+
root / JOURNAL_FILENAME,
|
|
1148
|
+
label="author_root.journal",
|
|
1149
|
+
maximum=MAX_AUTHORING_JOURNAL_BYTES,
|
|
1150
|
+
max_depth=MAX_PREDECESSOR_HEAD_DEPTH,
|
|
1151
|
+
max_members=MAX_STAGE_RECEIPT_MEMBERS,
|
|
1152
|
+
max_items=MAX_PREDECESSOR_RANGE_DESCRIPTORS,
|
|
1153
|
+
expected_sha256=expected_sha256,
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
def _verify_author_receipt(
|
|
1158
|
+
author: Mapping[str, Any],
|
|
1159
|
+
*,
|
|
1160
|
+
manifest: Mapping[str, Any],
|
|
1161
|
+
manifest_bytes: int,
|
|
1162
|
+
harvest: Mapping[str, Any],
|
|
1163
|
+
harvest_sha256: str,
|
|
1164
|
+
author_root: Path,
|
|
1165
|
+
execution: Mapping[str, Any],
|
|
1166
|
+
) -> dict[str, Any]:
|
|
1167
|
+
"""Re-derive an author receipt from its exact manifest and registered policy.
|
|
1168
|
+
|
|
1169
|
+
The receipt is carried into the authoritative composite, so none of its duplicated summaries
|
|
1170
|
+
may be accepted merely because the receipt bytes have a caller-supplied digest. This check is
|
|
1171
|
+
deliberately before the publication lock: a false receipt cannot create either output root.
|
|
1172
|
+
|
|
1173
|
+
What it returns is the generation evidence this command established for itself, which the
|
|
1174
|
+
composite states beside the carried receipt.
|
|
1175
|
+
"""
|
|
1176
|
+
|
|
1177
|
+
if set(author) != _AUTHOR_RECEIPT_MEMBERS:
|
|
1178
|
+
raise SourceContractError(
|
|
1179
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1180
|
+
"author_receipt",
|
|
1181
|
+
"the authoring receipt must carry exactly its closed contract members",
|
|
1182
|
+
)
|
|
1183
|
+
if set(manifest) != _AUTHOR_MANIFEST_MEMBERS:
|
|
1184
|
+
raise SourceContractError(
|
|
1185
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1186
|
+
"author_root.manifest",
|
|
1187
|
+
"the authoring manifest must carry exactly its closed contract members",
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
staging = manifest["staging"]
|
|
1191
|
+
coverage = manifest["coverage"]
|
|
1192
|
+
counts = manifest["counts"]
|
|
1193
|
+
layer_text_digests = manifest["layer_text_digests"]
|
|
1194
|
+
shards = manifest["shards"]
|
|
1195
|
+
limits = author["limits"]
|
|
1196
|
+
if not isinstance(staging, dict) or set(staging) != _AUTHOR_STAGING_MEMBERS:
|
|
1197
|
+
raise SourceContractError(
|
|
1198
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1199
|
+
"author_root.manifest.staging",
|
|
1200
|
+
"the authoring manifest must carry the complete staging coordinate",
|
|
1201
|
+
)
|
|
1202
|
+
if (
|
|
1203
|
+
not isinstance(counts, dict)
|
|
1204
|
+
or set(counts) != _AUTHOR_COUNT_MEMBERS
|
|
1205
|
+
or any(type(value) is not int or value < 0 for value in counts.values())
|
|
1206
|
+
):
|
|
1207
|
+
raise SourceContractError(
|
|
1208
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1209
|
+
"author_root.manifest.counts",
|
|
1210
|
+
"the authoring manifest must carry its exact non-negative count contract",
|
|
1211
|
+
)
|
|
1212
|
+
if not coverage_is_valid(coverage):
|
|
1213
|
+
raise SourceContractError(
|
|
1214
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1215
|
+
"author_root.manifest.coverage",
|
|
1216
|
+
"the authoring manifest must state what it covers and what it drew from",
|
|
1217
|
+
)
|
|
1218
|
+
if (
|
|
1219
|
+
not isinstance(layer_text_digests, dict)
|
|
1220
|
+
or set(layer_text_digests) != set(EMBEDDING_LAYERS)
|
|
1221
|
+
or any(value is not None and not is_digest(value) for value in layer_text_digests.values())
|
|
1222
|
+
):
|
|
1223
|
+
raise SourceContractError(
|
|
1224
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1225
|
+
"author_root.manifest.layer_text_digests",
|
|
1226
|
+
"the authoring manifest must bind every retrieval layer digest",
|
|
1227
|
+
)
|
|
1228
|
+
if not isinstance(shards, list) or len(shards) > MAX_RANGE_DESCRIPTORS:
|
|
1229
|
+
raise SourceContractError(
|
|
1230
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1231
|
+
"author_root.manifest.shards",
|
|
1232
|
+
"the authoring manifest shard population exceeds its fixed contract",
|
|
1233
|
+
)
|
|
1234
|
+
if (
|
|
1235
|
+
not isinstance(limits, dict)
|
|
1236
|
+
or set(limits) != _AUTHOR_LIMIT_MEMBERS
|
|
1237
|
+
or any(type(value) is not int or value < 1 for value in limits.values())
|
|
1238
|
+
or limits["max_shard_entries"] > MAX_AUTHORING_SHARD_ENTRIES
|
|
1239
|
+
or limits["max_shard_bytes"] > MAX_AUTHORING_SHARD_BYTES
|
|
1240
|
+
):
|
|
1241
|
+
raise SourceContractError(
|
|
1242
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1243
|
+
"author_receipt.limits",
|
|
1244
|
+
"the authoring receipt must carry its exact bounded limit contract",
|
|
1245
|
+
)
|
|
1246
|
+
|
|
1247
|
+
digest_staging = _AUTHOR_STAGING_MEMBERS - {
|
|
1248
|
+
"endpoint",
|
|
1249
|
+
"harvester_coordinate",
|
|
1250
|
+
"pass_number",
|
|
1251
|
+
"unique_records",
|
|
1252
|
+
"raw_response_index_count",
|
|
1253
|
+
"normalized_page_index_count",
|
|
1254
|
+
"record_index_count",
|
|
1255
|
+
}
|
|
1256
|
+
count_staging = {
|
|
1257
|
+
"unique_records",
|
|
1258
|
+
"raw_response_index_count",
|
|
1259
|
+
"normalized_page_index_count",
|
|
1260
|
+
"record_index_count",
|
|
1261
|
+
}
|
|
1262
|
+
if (
|
|
1263
|
+
any(not is_digest(staging[name]) for name in digest_staging)
|
|
1264
|
+
or not isinstance(staging["endpoint"], str)
|
|
1265
|
+
or not staging["endpoint"]
|
|
1266
|
+
or not isinstance(staging["harvester_coordinate"], str)
|
|
1267
|
+
or not staging["harvester_coordinate"]
|
|
1268
|
+
or type(staging["pass_number"]) is not int
|
|
1269
|
+
or staging["pass_number"] < 1
|
|
1270
|
+
or any(type(staging[name]) is not int or staging[name] < 0 for name in count_staging)
|
|
1271
|
+
):
|
|
1272
|
+
raise SourceContractError(
|
|
1273
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1274
|
+
"author_root.manifest.staging",
|
|
1275
|
+
"the authoring manifest staging coordinate has invalid members",
|
|
1276
|
+
)
|
|
1277
|
+
|
|
1278
|
+
manifest_body = {name: value for name, value in manifest.items() if name != "root_sha256"}
|
|
1279
|
+
if manifest["root_sha256"] != canonical_sha256(manifest_body):
|
|
1280
|
+
raise SourceContractError(
|
|
1281
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1282
|
+
"author_root.manifest.root_sha256",
|
|
1283
|
+
"the authoring manifest does not reproduce its own root coordinate",
|
|
1284
|
+
)
|
|
1285
|
+
try:
|
|
1286
|
+
policy = resolve_authoring_policy(manifest["policy_id"])
|
|
1287
|
+
except SourceContractError as error:
|
|
1288
|
+
raise SourceContractError(
|
|
1289
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1290
|
+
"author_root.manifest.policy_id",
|
|
1291
|
+
"the authoring manifest does not name a registered policy",
|
|
1292
|
+
) from error
|
|
1293
|
+
|
|
1294
|
+
expected = {
|
|
1295
|
+
"schema_version": AUTHORING_RECEIPT_SCHEMA,
|
|
1296
|
+
"status": "complete",
|
|
1297
|
+
"reason_code": None,
|
|
1298
|
+
"policy_id": manifest["policy_id"],
|
|
1299
|
+
"policy_sha256": manifest["policy_sha256"],
|
|
1300
|
+
"provider_id": policy.provider_id,
|
|
1301
|
+
"harvester_coordinate": policy.harvester_coordinate,
|
|
1302
|
+
"staging": staging,
|
|
1303
|
+
"coverage": coverage,
|
|
1304
|
+
"counts": counts,
|
|
1305
|
+
"layer_text_digests": layer_text_digests,
|
|
1306
|
+
"shard_count": len(shards),
|
|
1307
|
+
"shards": shards,
|
|
1308
|
+
"manifest_sha256": author["manifest_sha256"],
|
|
1309
|
+
"harvest_receipt_sha256": harvest_sha256,
|
|
1310
|
+
"output_root_sha256": root_coordinate(author_root),
|
|
1311
|
+
"execution": execution,
|
|
1312
|
+
}
|
|
1313
|
+
# The two populations a coverage statement names are not free either. It draws from the
|
|
1314
|
+
# corpus this generation's own staging coordinate found, and it accounts for every record
|
|
1315
|
+
# this generation's own shards hold. `coverage_is_valid` above closed the statement against
|
|
1316
|
+
# itself; this closes it against the generation it describes.
|
|
1317
|
+
if (
|
|
1318
|
+
coverage["corpus_records"] != staging["unique_records"]
|
|
1319
|
+
or coverage["selected_records"] != counts["records"]
|
|
1320
|
+
):
|
|
1321
|
+
raise SourceContractError(
|
|
1322
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1323
|
+
"author_root.manifest.coverage",
|
|
1324
|
+
"the stated coverage does not draw from this generation's corpus or does not "
|
|
1325
|
+
"account for every record it authored",
|
|
1326
|
+
)
|
|
1327
|
+
if manifest["policy_sha256"] != policy.digest:
|
|
1328
|
+
raise SourceContractError(
|
|
1329
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1330
|
+
"author_root.manifest.policy_sha256",
|
|
1331
|
+
"the authoring manifest policy fingerprint differs from its registered policy",
|
|
1332
|
+
)
|
|
1333
|
+
if staging["harvester_coordinate"] != policy.harvester_coordinate:
|
|
1334
|
+
raise SourceContractError(
|
|
1335
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1336
|
+
"author_root.manifest.staging.harvester_coordinate",
|
|
1337
|
+
"the staging coordinate differs from the registered authoring policy",
|
|
1338
|
+
)
|
|
1339
|
+
if (
|
|
1340
|
+
harvest.get("provider") != policy.provider_id
|
|
1341
|
+
or harvest.get("harvester_coordinate") != policy.harvester_coordinate
|
|
1342
|
+
):
|
|
1343
|
+
raise SourceContractError(
|
|
1344
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1345
|
+
"harvest_receipt",
|
|
1346
|
+
"the harvest receipt differs from the registered authoring policy",
|
|
1347
|
+
)
|
|
1348
|
+
for member, value in expected.items():
|
|
1349
|
+
if author[member] != value:
|
|
1350
|
+
raise SourceContractError(
|
|
1351
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1352
|
+
f"author_receipt.{member}",
|
|
1353
|
+
"the authoring receipt differs from its exact manifest or resolved input",
|
|
1354
|
+
)
|
|
1355
|
+
return _verify_author_generation_evidence(
|
|
1356
|
+
author, manifest=manifest, manifest_bytes=manifest_bytes, author_root=author_root
|
|
1357
|
+
)
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
def _verify_author_generation_evidence(
|
|
1361
|
+
author: Mapping[str, Any],
|
|
1362
|
+
*,
|
|
1363
|
+
manifest: Mapping[str, Any],
|
|
1364
|
+
manifest_bytes: int,
|
|
1365
|
+
author_root: Path,
|
|
1366
|
+
) -> dict[str, Any]:
|
|
1367
|
+
"""Bind the author receipt's journal, bounds and byte total to the generation it authored.
|
|
1368
|
+
|
|
1369
|
+
Everything above this point re-derives the receipt's *summaries* from the manifest. Three
|
|
1370
|
+
members are left over, and each one used to be believed on the strength of its own shape:
|
|
1371
|
+
the journal fingerprint, the bounds the invocation ran under, and the bytes it wrote.
|
|
1372
|
+
|
|
1373
|
+
Each is reconciled here against evidence the author root still holds. The journal digest is
|
|
1374
|
+
read back from the retained ``journal.json`` and the journal is required to describe exactly
|
|
1375
|
+
this manifest's generation. The bounds are required to admit the shards that were actually
|
|
1376
|
+
authored, which shows only that each was stated at least that wide. The byte total is an
|
|
1377
|
+
invocation-scoped measure -- a resumed authoring writes only what it added -- so it is
|
|
1378
|
+
reconciled against the interval the retained outputs allow: never less than the journal and
|
|
1379
|
+
manifest this invocation had to publish, and never more than the bytes that manifest and
|
|
1380
|
+
journal account for.
|
|
1381
|
+
|
|
1382
|
+
None of that reaches an exact value for six of the eight, so the returned evidence carries
|
|
1383
|
+
``AUTHOR_CLAIM_RECONCILIATION`` rather than a list of the unreachable ones: naming only what
|
|
1384
|
+
could not be reached would leave a reader to conclude that everything unnamed was.
|
|
1385
|
+
"""
|
|
1386
|
+
|
|
1387
|
+
if (
|
|
1388
|
+
not is_digest(author["journal_sha256"])
|
|
1389
|
+
or type(author["output_bytes"]) is not int
|
|
1390
|
+
or author["output_bytes"] < 0
|
|
1391
|
+
):
|
|
1392
|
+
raise SourceContractError(
|
|
1393
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1394
|
+
"author_receipt",
|
|
1395
|
+
"the authoring receipt carries invalid generation evidence",
|
|
1396
|
+
)
|
|
1397
|
+
shards = manifest["shards"]
|
|
1398
|
+
for descriptor in shards:
|
|
1399
|
+
if not shard_descriptor_is_valid(descriptor):
|
|
1400
|
+
raise SourceContractError(
|
|
1401
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1402
|
+
"author_root.manifest.shards[]",
|
|
1403
|
+
"an authored shard descriptor must carry exactly its closed contract members",
|
|
1404
|
+
)
|
|
1405
|
+
|
|
1406
|
+
journal_raw, journal = _author_journal(author_root, author["journal_sha256"])
|
|
1407
|
+
if (
|
|
1408
|
+
set(journal) != _AUTHOR_JOURNAL_MEMBERS
|
|
1409
|
+
or journal["schema_version"] != AUTHORING_JOURNAL_SCHEMA
|
|
1410
|
+
):
|
|
1411
|
+
raise SourceContractError(
|
|
1412
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1413
|
+
"author_root.journal",
|
|
1414
|
+
"the retained authoring journal must carry exactly its closed contract members",
|
|
1415
|
+
)
|
|
1416
|
+
if (
|
|
1417
|
+
journal["reason_code"] is not None
|
|
1418
|
+
or journal["policy_sha256"] != manifest["policy_sha256"]
|
|
1419
|
+
or journal["staging_checkpoint_sha256"] != manifest["staging"]["checkpoint_sha256"]
|
|
1420
|
+
or journal["coverage"] != manifest["coverage"]
|
|
1421
|
+
or journal["shards"] != shards
|
|
1422
|
+
or journal["counts"] != manifest["counts"]
|
|
1423
|
+
or journal["layer_text_digests"] != manifest["layer_text_digests"]
|
|
1424
|
+
or journal["last_provider_record_id"]
|
|
1425
|
+
!= (shards[-1]["last_provider_record_id"] if shards else None)
|
|
1426
|
+
):
|
|
1427
|
+
raise SourceContractError(
|
|
1428
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1429
|
+
"author_root.journal",
|
|
1430
|
+
"the retained authoring journal describes another authored generation",
|
|
1431
|
+
)
|
|
1432
|
+
|
|
1433
|
+
limits = author["limits"]
|
|
1434
|
+
if (
|
|
1435
|
+
limits["max_records"] < manifest["counts"]["records"]
|
|
1436
|
+
or any(limits["max_shard_entries"] < descriptor["item_count"] for descriptor in shards)
|
|
1437
|
+
or any(limits["max_shard_bytes"] < descriptor["bytes"] for descriptor in shards)
|
|
1438
|
+
):
|
|
1439
|
+
raise SourceContractError(
|
|
1440
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1441
|
+
"author_receipt.limits",
|
|
1442
|
+
"the authoring receipt states bounds the generation it authored exceeds",
|
|
1443
|
+
)
|
|
1444
|
+
|
|
1445
|
+
# One shard is one immutable digest-named member, so a repeated digest is one file.
|
|
1446
|
+
shard_bytes = sum({descriptor["sha256"]: descriptor["bytes"] for descriptor in shards}.values())
|
|
1447
|
+
published_bytes = len(journal_raw) + manifest_bytes
|
|
1448
|
+
accounted_root_bytes = shard_bytes + published_bytes
|
|
1449
|
+
if not published_bytes <= author["output_bytes"] <= accounted_root_bytes:
|
|
1450
|
+
raise SourceContractError(
|
|
1451
|
+
"PUBLISH_STAGE_HANDOFF",
|
|
1452
|
+
"author_receipt.output_bytes",
|
|
1453
|
+
"the authoring receipt states a byte total the authored root cannot account for",
|
|
1454
|
+
)
|
|
1455
|
+
return {
|
|
1456
|
+
"journal_sha256": author["journal_sha256"],
|
|
1457
|
+
"accounted_root_bytes": accounted_root_bytes,
|
|
1458
|
+
"claim_reconciliation": dict(AUTHOR_CLAIM_RECONCILIATION),
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
# ------------------------------------------------------------------------------------------------
|
|
1463
|
+
# Predecessor coordinates
|
|
1464
|
+
# ------------------------------------------------------------------------------------------------
|
|
1465
|
+
|
|
1466
|
+
|
|
1467
|
+
def _predecessor_coordinates(args: argparse.Namespace) -> dict[str, Any] | None:
|
|
1468
|
+
stated = [name for name in _PREDECESSOR_ARGUMENTS if getattr(args, name) is not None]
|
|
1469
|
+
if args.predecessor_mode == "none":
|
|
1470
|
+
if stated:
|
|
1471
|
+
raise SourceContractError(
|
|
1472
|
+
"PUBLISH_PREDECESSOR_COORDINATE",
|
|
1473
|
+
stated[0],
|
|
1474
|
+
"a generation with no predecessor takes no predecessor argument at all",
|
|
1475
|
+
)
|
|
1476
|
+
return None
|
|
1477
|
+
if len(stated) != len(_PREDECESSOR_ARGUMENTS):
|
|
1478
|
+
missing = [name for name in _PREDECESSOR_ARGUMENTS if getattr(args, name) is None]
|
|
1479
|
+
raise SourceContractError(
|
|
1480
|
+
"PUBLISH_PREDECESSOR_COORDINATE",
|
|
1481
|
+
missing[0],
|
|
1482
|
+
"an exact predecessor is named by all six of its roots and digests",
|
|
1483
|
+
)
|
|
1484
|
+
for name in (
|
|
1485
|
+
"predecessor_identity_digest",
|
|
1486
|
+
"predecessor_history_digest",
|
|
1487
|
+
"predecessor_catalog_digest",
|
|
1488
|
+
):
|
|
1489
|
+
if not is_digest(getattr(args, name)):
|
|
1490
|
+
raise SourceContractError(
|
|
1491
|
+
"PUBLISH_PREDECESSOR_COORDINATE", name, "must be a lowercase SHA-256"
|
|
1492
|
+
)
|
|
1493
|
+
return {
|
|
1494
|
+
"identity_root": Path(args.predecessor_identity_root),
|
|
1495
|
+
"identity_digest": args.predecessor_identity_digest,
|
|
1496
|
+
"history_root": Path(args.predecessor_history_root),
|
|
1497
|
+
"history_digest": args.predecessor_history_digest,
|
|
1498
|
+
"catalog_root": Path(args.predecessor_catalog_root),
|
|
1499
|
+
"catalog_digest": args.predecessor_catalog_digest,
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
def _read_predecessor_heads(
|
|
1504
|
+
predecessor: Mapping[str, Any] | None, *, identity_descriptor: int | None = None
|
|
1505
|
+
) -> dict[str, Any] | None:
|
|
1506
|
+
"""Read all three predecessor heads under the fixed caps, and cross-check what they claim."""
|
|
1507
|
+
|
|
1508
|
+
if predecessor is None:
|
|
1509
|
+
return None
|
|
1510
|
+
# The identity generation is opened through the exact manifest name its writer owns.
|
|
1511
|
+
if identity_descriptor is None:
|
|
1512
|
+
_raw, identity = load_bounded_json(
|
|
1513
|
+
predecessor["identity_root"] / IDENTITY_MANIFEST_FILENAME,
|
|
1514
|
+
label="predecessor_identity_root.manifest",
|
|
1515
|
+
maximum=MAX_PREDECESSOR_HEAD_BYTES,
|
|
1516
|
+
max_depth=MAX_PREDECESSOR_HEAD_DEPTH,
|
|
1517
|
+
max_members=MAX_STAGE_RECEIPT_MEMBERS,
|
|
1518
|
+
max_items=MAX_PREDECESSOR_RANGE_DESCRIPTORS,
|
|
1519
|
+
expected_sha256=predecessor["identity_digest"],
|
|
1520
|
+
)
|
|
1521
|
+
else:
|
|
1522
|
+
_raw, identity = load_bounded_json_at(
|
|
1523
|
+
identity_descriptor,
|
|
1524
|
+
IDENTITY_MANIFEST_FILENAME,
|
|
1525
|
+
label="predecessor_identity_root.manifest",
|
|
1526
|
+
maximum=MAX_PREDECESSOR_HEAD_BYTES,
|
|
1527
|
+
max_depth=MAX_PREDECESSOR_HEAD_DEPTH,
|
|
1528
|
+
max_members=MAX_STAGE_RECEIPT_MEMBERS,
|
|
1529
|
+
max_items=MAX_PREDECESSOR_RANGE_DESCRIPTORS,
|
|
1530
|
+
)
|
|
1531
|
+
if sha256_bytes(_raw) != predecessor["identity_digest"]:
|
|
1532
|
+
raise SourceContractError(
|
|
1533
|
+
"GENERATION_INPUT_DIGEST",
|
|
1534
|
+
"predecessor_identity_root.manifest",
|
|
1535
|
+
"the retained identity manifest differs from its required digest",
|
|
1536
|
+
)
|
|
1537
|
+
if identity.get("schema_version") != IDENTITY_MANIFEST_SCHEMA:
|
|
1538
|
+
raise SourceContractError(
|
|
1539
|
+
"PUBLISH_PREDECESSOR_COORDINATE",
|
|
1540
|
+
"predecessor_identity_root.manifest",
|
|
1541
|
+
"the predecessor identity root does not hold an identity generation",
|
|
1542
|
+
)
|
|
1543
|
+
identity_records = identity.get("record_count")
|
|
1544
|
+
if type(identity_records) is not int or identity_records < 0:
|
|
1545
|
+
raise SourceContractError(
|
|
1546
|
+
"PUBLISH_PREDECESSOR_COORDINATE",
|
|
1547
|
+
"predecessor_identity_root.manifest",
|
|
1548
|
+
"an identity generation states how many records it carries",
|
|
1549
|
+
)
|
|
1550
|
+
_raw, history = load_bounded_json(
|
|
1551
|
+
predecessor["history_root"] / DELTA_MANIFEST_FILENAME,
|
|
1552
|
+
label="predecessor_history_root.manifest",
|
|
1553
|
+
maximum=min(MAX_PREDECESSOR_HEAD_BYTES, MAX_DELTA_MANIFEST_BYTES),
|
|
1554
|
+
max_depth=MAX_PREDECESSOR_HEAD_DEPTH,
|
|
1555
|
+
max_members=MAX_STAGE_RECEIPT_MEMBERS,
|
|
1556
|
+
max_items=MAX_PREDECESSOR_RANGE_DESCRIPTORS,
|
|
1557
|
+
expected_sha256=predecessor["history_digest"],
|
|
1558
|
+
)
|
|
1559
|
+
if history.get("outputs", {}).get("identity_manifest_sha256") != predecessor["identity_digest"]:
|
|
1560
|
+
raise SourceContractError(
|
|
1561
|
+
"PUBLISH_PREDECESSOR_COORDINATE",
|
|
1562
|
+
"predecessor_history_root",
|
|
1563
|
+
"the predecessor's classification does not name the identity generation given here",
|
|
1564
|
+
)
|
|
1565
|
+
_raw, head = load_bounded_json(
|
|
1566
|
+
predecessor["catalog_root"] / "packed-head.json",
|
|
1567
|
+
label="predecessor_catalog_root.head",
|
|
1568
|
+
maximum=MAX_PREDECESSOR_HEAD_BYTES,
|
|
1569
|
+
max_depth=MAX_PREDECESSOR_HEAD_DEPTH,
|
|
1570
|
+
max_members=MAX_STAGE_RECEIPT_MEMBERS,
|
|
1571
|
+
max_items=MAX_PREDECESSOR_RANGE_DESCRIPTORS,
|
|
1572
|
+
expected_sha256=predecessor["catalog_digest"],
|
|
1573
|
+
)
|
|
1574
|
+
if head.get("generation_sha256") != predecessor["history_digest"]:
|
|
1575
|
+
raise SourceContractError(
|
|
1576
|
+
"PUBLISH_PREDECESSOR_COORDINATE",
|
|
1577
|
+
"predecessor_catalog_root.head.generation_sha256",
|
|
1578
|
+
"the predecessor packed catalog was not published from the named history generation",
|
|
1579
|
+
)
|
|
1580
|
+
ranges = head.get("ranges", ())
|
|
1581
|
+
if not isinstance(ranges, list) or len(ranges) > MAX_PREDECESSOR_RANGE_DESCRIPTORS:
|
|
1582
|
+
raise SourceContractError(
|
|
1583
|
+
"PUBLISH_PREDECESSOR_COORDINATE",
|
|
1584
|
+
"predecessor_catalog_root.head.ranges",
|
|
1585
|
+
f"a predecessor head states at most {MAX_PREDECESSOR_RANGE_DESCRIPTORS} ranges",
|
|
1586
|
+
)
|
|
1587
|
+
return {
|
|
1588
|
+
"identity_manifest_sha256": predecessor["identity_digest"],
|
|
1589
|
+
"identity_root_sha256": root_coordinate(predecessor["identity_root"]),
|
|
1590
|
+
"history_manifest_sha256": predecessor["history_digest"],
|
|
1591
|
+
"history_root_sha256": root_coordinate(predecessor["history_root"]),
|
|
1592
|
+
"catalog_head_sha256": predecessor["catalog_digest"],
|
|
1593
|
+
"catalog_root_sha256": root_coordinate(predecessor["catalog_root"]),
|
|
1594
|
+
"catalog_range_count": len(ranges),
|
|
1595
|
+
# The predecessor record count comes from the identity manifest's required member.
|
|
1596
|
+
"identity_record_count": identity_records,
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
|
|
1600
|
+
# ------------------------------------------------------------------------------------------------
|
|
1601
|
+
# Classification and publication
|
|
1602
|
+
# ------------------------------------------------------------------------------------------------
|
|
1603
|
+
|
|
1604
|
+
|
|
1605
|
+
def _classify(
|
|
1606
|
+
args: argparse.Namespace,
|
|
1607
|
+
*,
|
|
1608
|
+
author: Mapping[str, Any],
|
|
1609
|
+
harvest: Mapping[str, Any],
|
|
1610
|
+
delta_root: Path,
|
|
1611
|
+
output_descriptor: int,
|
|
1612
|
+
coordinate_root: Path | None = None,
|
|
1613
|
+
predecessor: Mapping[str, Any] | None,
|
|
1614
|
+
resumed: bool,
|
|
1615
|
+
predecessor_identity_descriptor: int | None = None,
|
|
1616
|
+
) -> dict[str, Any]:
|
|
1617
|
+
with retained_child_directory(
|
|
1618
|
+
output_descriptor,
|
|
1619
|
+
DELTA_DIRNAME,
|
|
1620
|
+
create=not resumed,
|
|
1621
|
+
label="delta.output",
|
|
1622
|
+
code_prefix="PUBLISH",
|
|
1623
|
+
refusal=SourceContractError,
|
|
1624
|
+
) as delta_descriptor:
|
|
1625
|
+
if resumed:
|
|
1626
|
+
try:
|
|
1627
|
+
raw, existing = load_bounded_json_at(
|
|
1628
|
+
delta_descriptor,
|
|
1629
|
+
DELTA_MANIFEST_FILENAME,
|
|
1630
|
+
label="delta.manifest",
|
|
1631
|
+
maximum=MAX_DELTA_MANIFEST_BYTES,
|
|
1632
|
+
max_depth=MAX_PREDECESSOR_HEAD_DEPTH,
|
|
1633
|
+
max_members=MAX_STAGE_RECEIPT_MEMBERS,
|
|
1634
|
+
max_items=MAX_PREDECESSOR_RANGE_DESCRIPTORS,
|
|
1635
|
+
)
|
|
1636
|
+
except SourceContractError as error:
|
|
1637
|
+
if error.code == "GENERATION_INPUT_PATH":
|
|
1638
|
+
raise SourceContractError(
|
|
1639
|
+
"PUBLISH_RESUME_RECEIPT",
|
|
1640
|
+
"classification",
|
|
1641
|
+
"the interrupted publication must retain its exact classification manifest",
|
|
1642
|
+
) from error
|
|
1643
|
+
raise
|
|
1644
|
+
return {
|
|
1645
|
+
"manifest_sha256": sha256_bytes(raw),
|
|
1646
|
+
"identity_manifest_sha256": existing["outputs"]["identity_manifest_sha256"],
|
|
1647
|
+
"counts": {name: existing["counts"][name] for name in DELTA_CLASSIFICATIONS},
|
|
1648
|
+
"reuse": existing["reuse"],
|
|
1649
|
+
"measures": existing["measures"],
|
|
1650
|
+
"shard_count": existing["outputs"]["shard_count"],
|
|
1651
|
+
"classification_digests": existing["outputs"]["classification_digests"],
|
|
1652
|
+
"identity_root_sha256": root_coordinate(
|
|
1653
|
+
(coordinate_root or delta_root) / DELTA_IDENTITY_DIRNAME
|
|
1654
|
+
),
|
|
1655
|
+
"history_root_sha256": root_coordinate(coordinate_root or delta_root),
|
|
1656
|
+
}
|
|
1657
|
+
measures = harvest["measures"]
|
|
1658
|
+
result = run_streaming_delta(
|
|
1659
|
+
authoring_root=Path(args.author_root),
|
|
1660
|
+
expected_authoring_manifest_sha256=author["manifest_sha256"],
|
|
1661
|
+
output_root=delta_root,
|
|
1662
|
+
output_descriptor=delta_descriptor,
|
|
1663
|
+
measures=SweepMeasures(
|
|
1664
|
+
requests=measures["requests"],
|
|
1665
|
+
responses=measures["responses"],
|
|
1666
|
+
pages=measures["pages"],
|
|
1667
|
+
provider_records=measures["provider_records"],
|
|
1668
|
+
),
|
|
1669
|
+
observed_at=_observed_at(harvest),
|
|
1670
|
+
limits=StreamingDeltaLimits(
|
|
1671
|
+
max_records=args.max_ranges * args.max_range_entries,
|
|
1672
|
+
max_disk_bytes=args.max_disk_bytes,
|
|
1673
|
+
max_wall_seconds=args.max_wall_seconds,
|
|
1674
|
+
),
|
|
1675
|
+
predecessor_root=None if predecessor is None else predecessor["identity_root"],
|
|
1676
|
+
expected_predecessor_manifest_sha256=(
|
|
1677
|
+
None if predecessor is None else predecessor["identity_digest"]
|
|
1678
|
+
),
|
|
1679
|
+
predecessor_root_descriptor=predecessor_identity_descriptor,
|
|
1680
|
+
)
|
|
1681
|
+
receipt = result.receipt
|
|
1682
|
+
return {
|
|
1683
|
+
"manifest_sha256": result.manifest_sha256,
|
|
1684
|
+
"identity_manifest_sha256": result.identity_manifest_sha256,
|
|
1685
|
+
"counts": result.counts,
|
|
1686
|
+
"reuse": receipt["reuse"],
|
|
1687
|
+
"measures": receipt["measures"],
|
|
1688
|
+
"shard_count": receipt["outputs"]["shard_count"],
|
|
1689
|
+
"classification_digests": receipt["outputs"]["classification_digests"],
|
|
1690
|
+
"identity_root_sha256": root_coordinate(
|
|
1691
|
+
(coordinate_root or delta_root) / DELTA_IDENTITY_DIRNAME
|
|
1692
|
+
),
|
|
1693
|
+
"history_root_sha256": root_coordinate(coordinate_root or delta_root),
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
|
|
1697
|
+
def _observed_at(harvest: Mapping[str, Any]) -> str:
|
|
1698
|
+
observed = harvest.get("observed_at")
|
|
1699
|
+
if isinstance(observed, str) and observed:
|
|
1700
|
+
return observed
|
|
1701
|
+
return datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
1702
|
+
|
|
1703
|
+
|
|
1704
|
+
def _backends(args: argparse.Namespace) -> list[Any]:
|
|
1705
|
+
backends: list[Any] = [LexicalHashingBackend()]
|
|
1706
|
+
if args.neural_backend is None:
|
|
1707
|
+
if args.neural_pin is not None or args.model_root is not None:
|
|
1708
|
+
raise SourceContractError(
|
|
1709
|
+
"PUBLISH_BACKEND_COORDINATE",
|
|
1710
|
+
"neural_backend",
|
|
1711
|
+
"a pinned model root belongs to the neural backend that reads it",
|
|
1712
|
+
)
|
|
1713
|
+
return backends
|
|
1714
|
+
if args.model_root is None or not is_digest(args.neural_pin):
|
|
1715
|
+
raise SourceContractError(
|
|
1716
|
+
"PUBLISH_BACKEND_COORDINATE",
|
|
1717
|
+
"neural_backend",
|
|
1718
|
+
"a neural representation names its governed model root and its exact pin",
|
|
1719
|
+
)
|
|
1720
|
+
from mostlyright.data_harness.sources.catalog.neural import resolve_local_neural_backend
|
|
1721
|
+
|
|
1722
|
+
backends.append(resolve_local_neural_backend(args.model_root))
|
|
1723
|
+
return backends
|
|
1724
|
+
|
|
1725
|
+
|
|
1726
|
+
def _publication_evidence(
|
|
1727
|
+
receipt: Mapping[str, Any], publication: Any, backends: Sequence[Any]
|
|
1728
|
+
) -> dict[str, Any]:
|
|
1729
|
+
del backends
|
|
1730
|
+
outputs = receipt["outputs"]
|
|
1731
|
+
return {
|
|
1732
|
+
# The publication receipt is carried whole, not summarised. A packed search authenticates
|
|
1733
|
+
# its store against exactly this document, so a reader that had to rebuild it from the
|
|
1734
|
+
# block below would be authenticating against a copy somebody made.
|
|
1735
|
+
"packed_receipt": dict(receipt),
|
|
1736
|
+
"coordinate_sha256": receipt["coordinate_sha256"],
|
|
1737
|
+
"expected_head_sha256": publication.head_sha256,
|
|
1738
|
+
"journal_sha256": publication.journal_sha256,
|
|
1739
|
+
"backends": list(receipt["backends"]),
|
|
1740
|
+
"posting_backend_coordinate": receipt["posting_backend_coordinate"],
|
|
1741
|
+
"adapter_mode": receipt["adapter_mode"],
|
|
1742
|
+
"predecessor": receipt["inputs"]["predecessor_head_sha256"],
|
|
1743
|
+
"counts": dict(publication.counts),
|
|
1744
|
+
"counters": dict(publication.counters),
|
|
1745
|
+
"counter_records": [dict(record) for record in receipt["invocations"]],
|
|
1746
|
+
"range_manifest_sha256s": [
|
|
1747
|
+
descriptor["manifest_sha256"] for descriptor in outputs["ranges"]
|
|
1748
|
+
],
|
|
1749
|
+
"ranges_chain_sha256": outputs["ranges_chain_sha256"],
|
|
1750
|
+
"vector_payload_sha256s": list(outputs["vector_member_sha256s"]),
|
|
1751
|
+
"posting_member_sha256s": list(outputs["posting_member_sha256s"]),
|
|
1752
|
+
"bound_member_sha256s": list(outputs["bound_member_sha256s"]),
|
|
1753
|
+
"residency": receipt["residency"],
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
|
|
1757
|
+
# ------------------------------------------------------------------------------------------------
|
|
1758
|
+
# Bounds
|
|
1759
|
+
# ------------------------------------------------------------------------------------------------
|
|
1760
|
+
|
|
1761
|
+
|
|
1762
|
+
def _validate_limits(args: argparse.Namespace) -> None:
|
|
1763
|
+
for name, exact in _FIXED.items():
|
|
1764
|
+
value = getattr(args, name)
|
|
1765
|
+
if type(value) is not int or value != exact:
|
|
1766
|
+
raise SourceContractError(
|
|
1767
|
+
"PUBLISH_LIMIT",
|
|
1768
|
+
name,
|
|
1769
|
+
f"this bound is fixed at {exact} and is not caller-widenable",
|
|
1770
|
+
)
|
|
1771
|
+
for name in (
|
|
1772
|
+
"max_batch_invocations",
|
|
1773
|
+
"max_encoded_layer_items",
|
|
1774
|
+
"max_backend_scalar_invocations",
|
|
1775
|
+
"max_encoded_input_bytes",
|
|
1776
|
+
"max_disk_bytes",
|
|
1777
|
+
"max_wall_seconds",
|
|
1778
|
+
):
|
|
1779
|
+
value = getattr(args, name)
|
|
1780
|
+
if type(value) is not int or value < 1:
|
|
1781
|
+
raise SourceContractError(
|
|
1782
|
+
"PUBLISH_LIMIT", name, "must be a positive canonical decimal integer"
|
|
1783
|
+
)
|
|
1784
|
+
|
|
1785
|
+
|
|
1786
|
+
def _verify_work_budget(args: argparse.Namespace, receipt: Mapping[str, Any]) -> None:
|
|
1787
|
+
"""The three work counters and the encoded input bytes, checked as totals and as sums.
|
|
1788
|
+
|
|
1789
|
+
The publisher owns these numbers -- it produces them from seam-minted records rather than
|
|
1790
|
+
accepting them -- so the caller's budget is checked against what was actually done, and against
|
|
1791
|
+
the ordered per-invocation records the composite receipt is about to state.
|
|
1792
|
+
"""
|
|
1793
|
+
|
|
1794
|
+
counters = receipt["counters"]
|
|
1795
|
+
for counter, cap in (
|
|
1796
|
+
("batch_invocations", args.max_batch_invocations),
|
|
1797
|
+
("encoded_layer_items", args.max_encoded_layer_items),
|
|
1798
|
+
("backend_scalar_invocations", args.max_backend_scalar_invocations),
|
|
1799
|
+
("encoded_utf8_bytes", args.max_encoded_input_bytes),
|
|
1800
|
+
):
|
|
1801
|
+
if counters[counter] > cap:
|
|
1802
|
+
raise SourceContractError(
|
|
1803
|
+
"PUBLISH_WORK_LIMIT",
|
|
1804
|
+
f"counters.{counter}",
|
|
1805
|
+
"the publication did more of this work than the caller allowed it to",
|
|
1806
|
+
)
|
|
1807
|
+
stated = sum(record[counter] for record in receipt["invocations"])
|
|
1808
|
+
if stated != counters[counter]:
|
|
1809
|
+
raise SourceContractError(
|
|
1810
|
+
"PUBLISH_WORK_LIMIT",
|
|
1811
|
+
f"counters.{counter}",
|
|
1812
|
+
"the ordered per-invocation records do not sum to the stated total",
|
|
1813
|
+
)
|
|
1814
|
+
|
|
1815
|
+
|
|
1816
|
+
def _limits_evidence(args: argparse.Namespace) -> dict[str, int]:
|
|
1817
|
+
return {
|
|
1818
|
+
**{name: getattr(args, name) for name in _FIXED},
|
|
1819
|
+
"max_batch_invocations": args.max_batch_invocations,
|
|
1820
|
+
"max_encoded_layer_items": args.max_encoded_layer_items,
|
|
1821
|
+
"max_backend_scalar_invocations": args.max_backend_scalar_invocations,
|
|
1822
|
+
"max_encoded_input_bytes": args.max_encoded_input_bytes,
|
|
1823
|
+
"max_disk_bytes": args.max_disk_bytes,
|
|
1824
|
+
"max_wall_seconds": args.max_wall_seconds,
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
|
|
1828
|
+
def _exit_code(payload: Mapping[str, Any]) -> int:
|
|
1829
|
+
return 0 if payload.get("status") == PUBLISH_COMPLETE_STATUS else 1
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
SPEC = CommandSpec(
|
|
1833
|
+
name="catalog-publish",
|
|
1834
|
+
help="classify one authored generation, publish it, and write its one composite receipt",
|
|
1835
|
+
add_arguments=add_arguments,
|
|
1836
|
+
run=run,
|
|
1837
|
+
exit_code=_exit_code,
|
|
1838
|
+
)
|