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,671 @@
|
|
|
1
|
+
"""The plain first line: what a person reads instead of the raise-site message.
|
|
2
|
+
|
|
3
|
+
A typed error is raised deep inside the harness, in the engineering wording the module around it
|
|
4
|
+
uses -- ``candidate run ancestor chain cannot be opened safely``. That wording is right where it is
|
|
5
|
+
written: it is precise, it is the same word the function names use, and it is what an engineer wants
|
|
6
|
+
in a bug report. It is wrong the moment it is the first line a person reads out of a terminal.
|
|
7
|
+
|
|
8
|
+
The one-direction rule resolves that by translating at the boundary rather than by renaming the
|
|
9
|
+
insides. This module is that translation. It answers one question -- *does this message use an
|
|
10
|
+
internal word?* -- and, when it does, hands back a plain sentence for the same failure instead.
|
|
11
|
+
|
|
12
|
+
Three things keep the translation honest:
|
|
13
|
+
|
|
14
|
+
* **Nothing is hidden.** The machine-readable object under ``--json`` still carries the raise-site
|
|
15
|
+
message verbatim, the typed code is still printed, and the plain block says where the exact
|
|
16
|
+
wording is. A person who wants the engineering sentence is one flag away from it.
|
|
17
|
+
* **A clean message is left alone.** Most refusals already name a column, a field path, or a check
|
|
18
|
+
and read perfectly well. Substituting those would lose detail and buy nothing, so a message with
|
|
19
|
+
no internal word comes through untouched.
|
|
20
|
+
* **The detail survives.** Where a message ends in a name -- a path, a member, a field -- that tail
|
|
21
|
+
is carried onto the plain sentence, because *which* file changed is the actionable half.
|
|
22
|
+
|
|
23
|
+
The term list here is the same list ``scripts/vocabulary_sweep.py`` reads out of
|
|
24
|
+
``scripts/vocabulary-sweep.d/_terms.json``; ``tests/test_ux_headline.py`` asserts the two are equal,
|
|
25
|
+
so the boundary enforces exactly the words the gate bans and no others.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import errno
|
|
31
|
+
import os
|
|
32
|
+
import re
|
|
33
|
+
from typing import NamedTuple
|
|
34
|
+
|
|
35
|
+
from mostlyright.data_harness.canonical import CanonicalJSONError
|
|
36
|
+
from mostlyright.data_harness.ux.path_kind import A_FOLDER, A_LINK
|
|
37
|
+
from mostlyright.data_harness.ux.remediation import COMPOUND_PREFIX_FAMILY, PREFIX_FAMILY
|
|
38
|
+
|
|
39
|
+
# --------------------------------------------------------------------------------------------
|
|
40
|
+
# The banned words
|
|
41
|
+
# --------------------------------------------------------------------------------------------
|
|
42
|
+
#
|
|
43
|
+
# Written one word per literal rather than as phrases. Every literal below has no whitespace in it,
|
|
44
|
+
# which is exactly the shape the vocabulary sweep treats as a name rather than as wording -- so the
|
|
45
|
+
# list that enforces the rule is not itself reported as a breach of it. That is a consequence of the
|
|
46
|
+
# sweep's stated rule, not an exclusion argued for this file.
|
|
47
|
+
|
|
48
|
+
INTERNAL_TERMS: tuple[tuple[str, ...], ...] = (
|
|
49
|
+
("candidate",),
|
|
50
|
+
("sealed", "candidate"),
|
|
51
|
+
("frozen", "recipe"),
|
|
52
|
+
("crawler",),
|
|
53
|
+
("trusted", "retriever"),
|
|
54
|
+
("sandbox",),
|
|
55
|
+
("normalizer",),
|
|
56
|
+
("normalizer", "child"),
|
|
57
|
+
("format", "family"),
|
|
58
|
+
("parser",),
|
|
59
|
+
("reader", "registry"),
|
|
60
|
+
("promotion", "gate"),
|
|
61
|
+
("golden", "fixture"),
|
|
62
|
+
("decode", "fingerprint"),
|
|
63
|
+
("acquisition", "receipt"),
|
|
64
|
+
("producer",),
|
|
65
|
+
("verifier",),
|
|
66
|
+
("byte-range", "acquisition"),
|
|
67
|
+
("strict", "206"),
|
|
68
|
+
("Content-Range", "validation"),
|
|
69
|
+
("watermark",),
|
|
70
|
+
("cycle", "cursor"),
|
|
71
|
+
("two-phase", "cycle", "pinning"),
|
|
72
|
+
("immutable", "version", "store"),
|
|
73
|
+
("latest", "pointer"),
|
|
74
|
+
("drift", "finding"),
|
|
75
|
+
("repair", "task"),
|
|
76
|
+
("workspace", "plan"),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _compile(words: tuple[str, ...]) -> re.Pattern[str]:
|
|
81
|
+
"""Word-boundary anchored, case-insensitive, tolerant of a trailing plural.
|
|
82
|
+
|
|
83
|
+
The same construction ``scripts/vocabulary_sweep.py`` uses, so a word the gate would report on
|
|
84
|
+
a page is the same word this boundary refuses to print.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
body = r"\s+".join(re.escape(word) for word in words)
|
|
88
|
+
return re.compile(rf"\b{body}s?\b", re.IGNORECASE)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
_PATTERNS: tuple[re.Pattern[str], ...] = tuple(_compile(words) for words in INTERNAL_TERMS)
|
|
92
|
+
|
|
93
|
+
# Characters that cannot appear in an ordinary English word. A token carrying one is a name -- a
|
|
94
|
+
# path, a flag, a field path, a placeholder -- and a name is never wording, so `candidate` inside
|
|
95
|
+
# `.work/run/candidate/manifest.json` is not a word anyone chose to write in a sentence.
|
|
96
|
+
_NAME_CHARACTERS = frozenset("\\_=$[]{}<>|@")
|
|
97
|
+
_DOTTED_NAME = re.compile(r"[A-Za-z0-9]\.[A-Za-z0-9]")
|
|
98
|
+
# A slash on its own is not enough. `candidate/run child namespace` and `assignment/candidate` are
|
|
99
|
+
# two words with a slash between them -- wording, and exactly the wording this boundary exists to
|
|
100
|
+
# replace. A slash means "path" only when the token also looks like one: rooted, or carrying a
|
|
101
|
+
# suffix or another name character.
|
|
102
|
+
_ROOTED_PATH = re.compile(r"^(?:[.~]{0,2}/|[A-Za-z]:[\\/])")
|
|
103
|
+
|
|
104
|
+
# What a removed name is replaced with before matching. It has to be something no term pattern can
|
|
105
|
+
# join across, or blanking `sealed <name> candidate` would manufacture the phrase `sealed candidate`
|
|
106
|
+
# that nobody wrote.
|
|
107
|
+
_NAME_PLACEHOLDER = "..."
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _is_name(token: str) -> bool:
|
|
111
|
+
"""Whether one whitespace-delimited token is a name rather than a word."""
|
|
112
|
+
|
|
113
|
+
if _NAME_CHARACTERS & set(token) or _DOTTED_NAME.search(token) is not None:
|
|
114
|
+
return True
|
|
115
|
+
return "/" in token and _ROOTED_PATH.match(token) is not None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def carries_internal_wording(text: str) -> bool:
|
|
119
|
+
"""Whether the prose in ``text`` uses one of the internal words, ignoring names inside it."""
|
|
120
|
+
|
|
121
|
+
prose = " ".join(_NAME_PLACEHOLDER if _is_name(token) else token for token in text.split())
|
|
122
|
+
return any(pattern.search(prose) for pattern in _PATTERNS)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# --------------------------------------------------------------------------------------------
|
|
126
|
+
# The plain sentences
|
|
127
|
+
# --------------------------------------------------------------------------------------------
|
|
128
|
+
#
|
|
129
|
+
# Every entry is a sentence body with no closing full stop, because the renderer adds one when the
|
|
130
|
+
# failure had no detail to name and a colon when it did. Written for the reader of a terminal: what
|
|
131
|
+
# stopped, in the words the rest of the product uses.
|
|
132
|
+
|
|
133
|
+
_LAST_RESORT = "That command stopped before it finished"
|
|
134
|
+
|
|
135
|
+
# One per family in `remediation.FAMILIES`. The safety net: a code nobody has curated still reads
|
|
136
|
+
# plainly, because its prefix has a family and every family has a sentence.
|
|
137
|
+
FAMILY_HEADLINES: dict[str, str] = {
|
|
138
|
+
"ingestion": "A resumable source copy could not safely advance",
|
|
139
|
+
"hosted lane": (
|
|
140
|
+
"That work on the Mostly Right backend could not be started, watched, or fetched"
|
|
141
|
+
),
|
|
142
|
+
"sources": "A source this Recipe names could not be used",
|
|
143
|
+
"rights": "That source may not be used on these terms",
|
|
144
|
+
"connectors": "The connector that reaches that source could not be used",
|
|
145
|
+
"cadence": "The record of how often that source publishes could not be added to",
|
|
146
|
+
"capture": "A recording of a live source could not be sealed or read back",
|
|
147
|
+
"reading": "That file could not be opened",
|
|
148
|
+
"cleaning": "A cleaning step could not be carried out",
|
|
149
|
+
"joins": "The join between two sources did not hold",
|
|
150
|
+
"quality": "A check this Build has to pass did not pass",
|
|
151
|
+
"recipes": "A Recipe, its approval, or its run settings is not valid",
|
|
152
|
+
"json": "That file is not JSON the harness can read",
|
|
153
|
+
"authoring": "That YAML file could not be read as a Recipe",
|
|
154
|
+
"workbench": "This Workbench folder is not in a state this command can use",
|
|
155
|
+
"workbench record": "The sealed control tree of this Workbench folder could not be read",
|
|
156
|
+
"review": "The sealed review of this Build could not be made or read",
|
|
157
|
+
"output": "That path could not be written to",
|
|
158
|
+
"sealing": "The sealed Build could not be read or written safely",
|
|
159
|
+
"network": "Fetching those bytes did not succeed",
|
|
160
|
+
"platform": "This machine is missing filesystem features this step needs",
|
|
161
|
+
"handoff": "Packaging this Build for Studio, or reading a job back, did not succeed",
|
|
162
|
+
"hosted authoring": "One hosted recipe proposal could not be authored from its bundle",
|
|
163
|
+
"hosted resources": "This hosted worker does not have enough bounded memory for graph work",
|
|
164
|
+
"dataset handoff": "Bringing the released Build back here did not finish",
|
|
165
|
+
"source catalog": "That entry in the Source Catalog could not be written",
|
|
166
|
+
"catalog fill": "Filling the Source Catalog from that provider did not succeed",
|
|
167
|
+
"catalog authoring": "The entries of that public catalog could not be written",
|
|
168
|
+
"catalog generation": "That generation of the public catalog could not be published",
|
|
169
|
+
"catalog update": "That published catalog release could not be installed on this computer",
|
|
170
|
+
"local search": "That search over what is already on this machine could not be answered",
|
|
171
|
+
"search model": "The search model on this machine could not be used",
|
|
172
|
+
"login": "The credential on this machine could not be used",
|
|
173
|
+
"viewer": "The local notebook viewer could not be started or used",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# One per code whose raise sites use an internal word today. Ordered by prefix, and within a prefix
|
|
177
|
+
# by the code itself, so the list reads as a table rather than as a history.
|
|
178
|
+
HEADLINES: dict[str, str] = {
|
|
179
|
+
"GRAPH_PACKAGE_UNSUPPORTED": "This graph Recipe cannot use packaged inputs yet",
|
|
180
|
+
# Opening a Build folder and everything under it.
|
|
181
|
+
# Neither headline says "folder" because it is printed unchanged with no path observation,
|
|
182
|
+
# and both codes are reached
|
|
183
|
+
# at paths that are not folders: `CANDIDATE_ABSENT` where nothing is at the path at all, and
|
|
184
|
+
# `CANDIDATE_ANCESTOR_INVALID` on the `ENOTDIR` that a plain file, a pipe, a bound socket and a
|
|
185
|
+
# character device all give. "There is no Build in that folder" and "The Build folder could not
|
|
186
|
+
# be opened" were the first line a person read in every one of those cases.
|
|
187
|
+
"CANDIDATE_ABSENT": "Nothing here can be read as a Build",
|
|
188
|
+
"CANDIDATE_ANCESTOR_INVALID": "The Build could not be opened",
|
|
189
|
+
"CANDIDATE_ANCESTOR_RACE": "The folders leading to the Build changed while they were "
|
|
190
|
+
"being opened",
|
|
191
|
+
"CANDIDATE_ANCESTOR_SYMLINK": "A folder on the way to the Build is a link",
|
|
192
|
+
"CANDIDATE_CONSTRUCTION_INVALID": "The Build could not be written out safely",
|
|
193
|
+
"CANDIDATE_CONSTRUCTION_UNSUPPORTED": "This machine cannot write a Build safely",
|
|
194
|
+
"CANDIDATE_DIGEST_MISMATCH": "The Build fingerprint does not match",
|
|
195
|
+
"CANDIDATE_ENVELOPE_INVALID": "The packaged Build is not valid",
|
|
196
|
+
"CANDIDATE_HASH_MISMATCH": "A file inside the Build changed",
|
|
197
|
+
"CANDIDATE_INPUT_REQUIRED": "This job has nothing to build from",
|
|
198
|
+
"CANDIDATE_INSTALL_INVALID": "The Build that was put in place is not the one that was made",
|
|
199
|
+
"CANDIDATE_INVALID": "This Build is missing something it has to have",
|
|
200
|
+
"CANDIDATE_LIMIT_INVALID": "A size limit this Build is held to is not one the harness can use",
|
|
201
|
+
"PROFILE_INVALID": "The Build's column summary could not be made into the service's own",
|
|
202
|
+
"CANDIDATE_MEMBER_INVALID": "A file inside the Build could not be read safely",
|
|
203
|
+
"CANDIDATE_MEMBER_MISMATCH": "The files in the Build are not the files its Receipt lists",
|
|
204
|
+
"CANDIDATE_MISSING": "The record points at a Build that is not there",
|
|
205
|
+
"CANDIDATE_NAMESPACE_RACE": "The Build folder changed while it was being checked",
|
|
206
|
+
"CANDIDATE_NOT_COMMITTED": "This Workbench folder has no finished Build yet",
|
|
207
|
+
"CANDIDATE_PLATFORM_UNSUPPORTED": "This machine is missing filesystem features this step needs",
|
|
208
|
+
"CANDIDATE_PRODUCER_MISMATCH": "The Build already there was not made by this job",
|
|
209
|
+
"CANDIDATE_RACE": "The Build changed while it was being read",
|
|
210
|
+
"CANDIDATE_REPLAY_MISMATCH": "Replaying the Build did not reproduce its files",
|
|
211
|
+
"CANDIDATE_SCOPE_MISMATCH": "That Build does not belong to the job that asked for it",
|
|
212
|
+
"CANDIDATE_SNAPSHOT_INVALID": "The Build moved while it was being read",
|
|
213
|
+
"CANDIDATE_STATE_MISMATCH": "The Build does not match this Workbench folder's record",
|
|
214
|
+
"CANDIDATE_TOO_LARGE": "This Build is larger than the harness will handle",
|
|
215
|
+
# The Courier, and what it brought back.
|
|
216
|
+
"CRAWLER_ADAPTER_UNAVAILABLE": "The Courier has no approved way to reach that source",
|
|
217
|
+
"CRAWLER_DOWNLOAD_FAILED": "The Courier could not fetch those bytes",
|
|
218
|
+
"CRAWLER_DOWNLOAD_INVALID": "What the Courier fetched is not what was asked for",
|
|
219
|
+
"CRAWLER_ENVIRONMENT_FORBIDDEN": "The Courier was handed a setting it is not allowed to carry",
|
|
220
|
+
"CRAWLER_NORMALIZATION_INPUT_INVALID": "What the Courier brought back could not be turned "
|
|
221
|
+
"into exact bytes",
|
|
222
|
+
"CRAWLER_POLICY_MISMATCH": "The rules the Courier was given for what it may fetch are not "
|
|
223
|
+
"the ones in force",
|
|
224
|
+
"CRAWLER_POLICY_REQUIRED": "The rules for what the Courier may fetch are not available",
|
|
225
|
+
"CRAWLER_POLL_TIMEOUT": "The Courier did not answer in time",
|
|
226
|
+
"CRAWLER_PROTOCOL_BINDING": "The Courier's answer is not tied to the request that was sent",
|
|
227
|
+
"CRAWLER_PROTOCOL_FAILURE": "The Courier returned a failure this harness cannot safely carry",
|
|
228
|
+
"CRAWLER_PROTOCOL_LIMIT": "That message to or from the Courier is larger than the harness "
|
|
229
|
+
"will carry",
|
|
230
|
+
"CRAWLER_PROTOCOL_SCHEMA": "The Courier's failure message uses a version this harness does "
|
|
231
|
+
"not know",
|
|
232
|
+
"ACQUIRE_HOSTED_COLLECTION_UNSUPPORTED": "This bounded collection needs the local attested "
|
|
233
|
+
"clean room",
|
|
234
|
+
"HOSTED_COLLECTION_UNSUPPORTED": "This bounded collection needs the local attested clean room",
|
|
235
|
+
"CRAWLER_PROTOCOL_TYPE": "The Courier's answer is not the shape an answer has to be",
|
|
236
|
+
"CRAWLER_PROTOCOL_VERSION": "The Courier is speaking a version this harness does not know",
|
|
237
|
+
"CRAWLER_REMOTE_FAILED": "The Courier reported a failure",
|
|
238
|
+
"CRAWLER_REMOTE_REQUIRED": "This fetch has to go through the Courier",
|
|
239
|
+
"CRAWLER_RESULT_INVALID": "The Courier's answer is not valid",
|
|
240
|
+
"CRAWLER_QUERY_INVALID": "The Courier was given a source request it cannot safely run",
|
|
241
|
+
"CRAWLER_SESSION_INVALID": "The Courier's session is not valid",
|
|
242
|
+
"CRAWLER_SESSION_SCOPE_MISMATCH": "The Courier's session belongs to a different job",
|
|
243
|
+
"SOURCE_CADENCE_CONTEXT_INVALID": "The source update evidence does not match this job",
|
|
244
|
+
"SOURCE_ADAPTER_UNREGISTERED": "No approved hosted adapter can refresh that source",
|
|
245
|
+
"CRAWLER_SOURCE_SCOPE_MISMATCH": "The Courier fetched a source this job did not name",
|
|
246
|
+
# Reading a source's bytes for one question in a research session.
|
|
247
|
+
"PROBE_SOURCE_ACQUISITION_MISBOUND": "The fetched source does not belong to the question that "
|
|
248
|
+
"asked for it",
|
|
249
|
+
"PROBE_SOURCE_RESULT_INVALID": "What came back for that source does not match what was fetched",
|
|
250
|
+
# Packaging for Studio, and the jobs it hands back.
|
|
251
|
+
"DEPLOY_REQUEST_INVALID": "This Recipe cannot be sent to Studio in its current form",
|
|
252
|
+
"DEPLOY_WORKER_POLICY_MISMATCH": "This Recipe would be checked under different rules after "
|
|
253
|
+
"it is sent",
|
|
254
|
+
"ARTIFACT_DOWNLOAD_BUDGET_EXCEEDED": "That download is larger than this job allows",
|
|
255
|
+
"ARTIFACT_SCOPE_MISMATCH": "That file belongs to a different job",
|
|
256
|
+
"ARTIFACT_SPLIT_VIEW": "That file read back differently the second time",
|
|
257
|
+
"BOOTSTRAP_CONFIG_INVALID": "The start-up settings are not valid",
|
|
258
|
+
"JOB_INVALID": "The job description is not valid",
|
|
259
|
+
"MANIFEST_INVALID": "The Receipt is not valid",
|
|
260
|
+
"PLAN_SCOPE_MISMATCH": "That plan belongs to a different job",
|
|
261
|
+
"PREDECESSOR_SCOPE_MISMATCH": "The Build this one follows belongs to a different job",
|
|
262
|
+
"PRODUCER_MISMATCH": "The Builder that ran is not the Builder this job enrolled",
|
|
263
|
+
"SESSION_LEASE_EXPIRY_UNREASONABLE": "This session has more time than this worker may "
|
|
264
|
+
"safely use",
|
|
265
|
+
"SIGNED_SESSION_SCOPE_MISMATCH": "That signed session belongs to a different job",
|
|
266
|
+
"SIGNING_KEY_STALE": "That signing key is no longer the enrolled one",
|
|
267
|
+
"VERIFIER_SCOPE_INVALID": "The Checker's assignment is not valid",
|
|
268
|
+
"VERIFIER_SCOPE_STALE": "The Checker's assignment is out of date",
|
|
269
|
+
# Reaching a source through its connector, and what the Clean room hands back.
|
|
270
|
+
"ACQUIRED_SNAPSHOT": "The saved copy of that source does not match its Receipt",
|
|
271
|
+
"ACQUISITION_SNAPSHOT": "The saved copy of that source could not be read safely",
|
|
272
|
+
"AUTHENTICATED_SOURCE_REQUIRES_TRUSTED_GATEWAY": "That source needs a sign-in, and the "
|
|
273
|
+
"Courier is never given one",
|
|
274
|
+
"REJECTED_NOT_RECEIPT": "Content that was refused cannot be given a Receipt saying it was "
|
|
275
|
+
"admitted",
|
|
276
|
+
"SANDBOX_ATTESTATION_MISMATCH": "The Clean room ran under different rules from the ones "
|
|
277
|
+
"pinned for this job",
|
|
278
|
+
"SANDBOX_ATTESTATION_REQUIRED": "This fetch needs the Clean room rules pinned for this job, "
|
|
279
|
+
"and none were given",
|
|
280
|
+
"SANDBOX_RESULT": "What came out of the Clean room is not tied to the exact bytes it was given",
|
|
281
|
+
"SNAPSHOT_CONTENT": "The saved copy of that source does not match its Receipt",
|
|
282
|
+
"STREAM_CONTENT_BINDING": "What came out of the Clean room is not tied to the exact stream "
|
|
283
|
+
"bytes",
|
|
284
|
+
"STREAM_QUERY_BINDING": "What came back from that stream does not match what was asked for",
|
|
285
|
+
"STREAM_STATE": "The record of where that stream had got to is not valid",
|
|
286
|
+
"STREAM_STATE_LIMIT": "The record of what that stream has already been shown is larger than "
|
|
287
|
+
"the harness will hold",
|
|
288
|
+
# Opening the bytes of a source file.
|
|
289
|
+
"PARSE_EXTERNAL_REFERENCE": "That file points at something outside itself, and the Reader "
|
|
290
|
+
"will not follow it",
|
|
291
|
+
"PARSE_INPUT": "The Reader was not given exact bytes to read",
|
|
292
|
+
"PARSE_INPUT_LIMIT": "That file is empty, or larger than the Reader will open",
|
|
293
|
+
"PARSE_MEDIA_MISMATCH": "The declared file type does not match the Reader that was chosen",
|
|
294
|
+
"PARSE_SUFFIX_MISMATCH": "The filename ending does not match the Reader that was chosen",
|
|
295
|
+
"READER_CONTRACT": "That file does not meet the Reader's required shape",
|
|
296
|
+
"READER_OPTIONS": "Those Reader settings are not valid for this file",
|
|
297
|
+
# A Recipe, its pinned inputs, and the evidence a run leaves behind.
|
|
298
|
+
"CONTEXT_REQUIRED": "This Recipe run is missing part of the package it has to be given",
|
|
299
|
+
"TABLE_PREVIEW_INVALID": "The preview of this table is not valid",
|
|
300
|
+
"ENGINE_NOT_ALLOWLISTED": "That engine is not one this harness will build with",
|
|
301
|
+
"ENGINE_RUNTIME_MISMATCH": "The engine that ran is not the engine recorded",
|
|
302
|
+
"RECIPE_CANDIDATE": "That Receipt was not written by a Recipe run",
|
|
303
|
+
"RECIPE_MATERIALIZATION": "What was written out does not match the Recipe",
|
|
304
|
+
"RECIPE_PREDECESSOR": "This run does not follow on from the last good one",
|
|
305
|
+
"RECIPE_TEMPLATE": "This Recipe does not match the approved template",
|
|
306
|
+
"RECIPE_RECEIPT_FIELDS": "That Receipt names a version it does not have the fields for",
|
|
307
|
+
"RECIPE_RESULT_MISMATCH": "The sealed Recipe evidence does not match what was built",
|
|
308
|
+
"RECIPE_SOURCE_CLASSIFICATION": "A source's exact bytes were refused by this Recipe's rules",
|
|
309
|
+
"RECIPE_SOURCE_STATE_INVALID": "The record of which sources were used is not exact",
|
|
310
|
+
"RECIPE_SOURCE_STATE_MISMATCH": "The sources used do not match the ones replayed independently",
|
|
311
|
+
"RECIPE_WATERMARK": "The Bookmark for a source is not where this run needs it to be",
|
|
312
|
+
"RECOMMENDATION_PARTITION": "The suggested source list is not split into valid groups",
|
|
313
|
+
"REQUIREMENT_BINDING": "The suggested sources do not match this question's requirements",
|
|
314
|
+
"UNSUPPORTED": "This question was answered as one the harness cannot support, so nothing "
|
|
315
|
+
"can be built from it",
|
|
316
|
+
"URL_HOST": "That source address does not name a valid host",
|
|
317
|
+
# The sealed review of a Build: who was enrolled to review it, what they signed, and what was
|
|
318
|
+
# sealed from it. Only the refusals whose own wording uses an internal word are here; the rest
|
|
319
|
+
# of `review.py` reads plainly already and comes through untouched.
|
|
320
|
+
"ASSIGNMENT_ATTEMPTS_NOT_DISTINCT": "One attempt at this job is enrolled in two roles of its "
|
|
321
|
+
"review",
|
|
322
|
+
"ASSIGNMENT_CANDIDATE_MISMATCH": "This review was assigned for a different Build",
|
|
323
|
+
"ASSIGNMENT_CYCLE_COUNT_INVALID": "This review assignment records a repair round, and this "
|
|
324
|
+
"version reviews first builds only",
|
|
325
|
+
"ASSIGNMENT_KEYS_NOT_DISTINCT": "The reviewers and the Checker have to sign with different "
|
|
326
|
+
"keys",
|
|
327
|
+
"ASSIGNMENT_PRINCIPALS_NOT_DISTINCT": "One person cannot hold two roles in one review",
|
|
328
|
+
"ASSIGNMENT_PRODUCER_ATTEMPT_MISMATCH": "This review was assigned for a different build "
|
|
329
|
+
"attempt",
|
|
330
|
+
"ASSIGNMENT_PRODUCER_MISMATCH": "This review was assigned for a build by somebody else",
|
|
331
|
+
"ASSIGNMENT_VERIFIER_KEY_MISMATCH": "That is not the signing key this review enrolled for the "
|
|
332
|
+
"Checker",
|
|
333
|
+
"ASSIGNMENT_VERIFIER_NOT_ENROLLED": "The Checker is not the one this review enrolled",
|
|
334
|
+
"DECISION_SNAPSHOT_MISSING": "The Build was not held still while it was checked",
|
|
335
|
+
"REPORT_CYCLE_COUNT_INVALID": "That review report records a repair round, and this version "
|
|
336
|
+
"reviews first builds only",
|
|
337
|
+
"REPORT_FIELD_MISMATCH": "That review report is about a different Build or a different "
|
|
338
|
+
"assignment",
|
|
339
|
+
# The Workbench folder's own durable record.
|
|
340
|
+
"RESUME_NOT_STARTED": "This Workbench folder has not been run yet, so there is nothing to "
|
|
341
|
+
"pick up",
|
|
342
|
+
"REVIEW_CANDIDATE_MISMATCH": "The review points at a different Build",
|
|
343
|
+
"RUN_STATE_INVALID": "This Workbench folder's record is not valid",
|
|
344
|
+
# Searching the Source Catalog and the private index over the Builds on this machine, and the
|
|
345
|
+
# record each search keeps of what it looked at. Only the refusals whose own wording uses an
|
|
346
|
+
# internal word are here; the rest of that area reads plainly and comes through untouched.
|
|
347
|
+
"CATALOG_TRACE_CANDIDATES": "The record of what this search looked at is not exact",
|
|
348
|
+
"CATALOG_TRACE_PARTITION": "The groups this search sorted its results into are not the "
|
|
349
|
+
"results it had",
|
|
350
|
+
"LOCAL_SEARCH_SERVICE_BINDING": "The Build this search chose is not the one that was "
|
|
351
|
+
"re-checked",
|
|
352
|
+
"LOCAL_SEARCH_TRACE_BINDING": "The record of this search does not match the Builds it read",
|
|
353
|
+
"NEURAL_PIN_MISMATCH": "The search model on this machine is not the one it is pinned to",
|
|
354
|
+
# Filling the Source Catalog and writing its entries. Both of these say "the payload", "the
|
|
355
|
+
# facts and citation" or "the parser" in their own wording, which is a sentence written for
|
|
356
|
+
# somebody reading the code rather than for somebody who ran a command.
|
|
357
|
+
"CATALOG_DELTA_RECORD_BINDING": "That catalog entry says something the provider's record does "
|
|
358
|
+
"not",
|
|
359
|
+
"HARVEST_XML_ENCODING": "That provider's answer is not written in the text encoding it says "
|
|
360
|
+
"it is",
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
# For the refusals raised without a typed code at all, keyed by the class that carries them.
|
|
364
|
+
# `naming` resolves a class through its hierarchy, so a subclass answers through its parent's
|
|
365
|
+
# sentence unless it is written here itself.
|
|
366
|
+
EXCEPTION_HEADLINES: dict[str, str] = {
|
|
367
|
+
"ReviewError": "This review was not accepted",
|
|
368
|
+
"SignerProviderError": "That signing key could not be used",
|
|
369
|
+
# An `OSError` the harness raised itself, rather than one the operating system handed it. The
|
|
370
|
+
# ones the system raises say things like "No such file or directory" and are already plain, so
|
|
371
|
+
# they never reach this map; the ones written here are safety refusals from putting a Build in
|
|
372
|
+
# place or starting a Clean room, and they read like the inside of the machine.
|
|
373
|
+
"OSError": "This step could not be completed safely on this machine",
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class SystemRefusal(NamedTuple):
|
|
378
|
+
"""What a person reads for one errno, and the key its remediation is written under.
|
|
379
|
+
|
|
380
|
+
One record rather than two tables. The sentence lived here and the remediation key lived in
|
|
381
|
+
`cli.py`, in a second `if` chain over the same errnos, and nothing held the two together: an
|
|
382
|
+
errno with a sentence and no key printed advice-free, an errno with a key and no sentence
|
|
383
|
+
printed as a number. They are the same decision and they are made once.
|
|
384
|
+
|
|
385
|
+
``observed`` is what the errno itself establishes, in the nouns :mod:`ux.path_kind` publishes,
|
|
386
|
+
and ``None`` where it establishes nothing. It is declared here rather than borrowed from
|
|
387
|
+
``code`` because one code serves two errnos that found different things: ``EISDIR`` is the
|
|
388
|
+
kernel saying it is a directory, ``ENOTDIR`` is the kernel saying only that a component is not
|
|
389
|
+
one, and both answer through ``PATH_NOT_A_FILE``. ``scripts/path_kind_gate.py`` reads this
|
|
390
|
+
field and refuses a headline that names any other kind.
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
headline: str
|
|
394
|
+
code: str
|
|
395
|
+
observed: str | None = None
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# Refusals a filesystem step was stopped by, keyed by errno. An errno message is English already --
|
|
399
|
+
# that is why it never reaches the term map above -- but `str(OSError)` is not that message: it is
|
|
400
|
+
# `[Errno 62] Too many levels of symbolic links: '/some/path'`, which opens with a number in
|
|
401
|
+
# brackets and quotes the path like a value in a log. `OSError.filename` carries that path as a
|
|
402
|
+
# value, so the line a person reads can be a sentence with the path at the end of it.
|
|
403
|
+
#
|
|
404
|
+
# Every entry is one a workbench command can surface, including self-referential links and names
|
|
405
|
+
# longer than the filesystem accepts.
|
|
406
|
+
SYSTEM_REFUSALS: dict[int, SystemRefusal] = {
|
|
407
|
+
errno.EACCES: SystemRefusal("This machine did not allow that", "PERMISSION_DENIED"),
|
|
408
|
+
# `EISDIR` is the kernel saying the target is a directory, so the noun is the refusal itself.
|
|
409
|
+
errno.EISDIR: SystemRefusal(
|
|
410
|
+
"That path is a folder, and this command needs a file", "PATH_NOT_A_FILE", observed=A_FOLDER
|
|
411
|
+
),
|
|
412
|
+
# `ELOOP` is the kernel saying it followed links until it gave up, so the noun is the refusal.
|
|
413
|
+
errno.ELOOP: SystemRefusal(
|
|
414
|
+
"That path is a link that leads back to itself", "PATH_LINK_LOOP", observed=A_LINK
|
|
415
|
+
),
|
|
416
|
+
errno.ENAMETOOLONG: SystemRefusal(
|
|
417
|
+
"That name is longer than this machine allows", "PATH_NAME_TOO_LONG"
|
|
418
|
+
),
|
|
419
|
+
# `ENOENT` does not establish that a file or folder is absent: the kernel says the *name did not
|
|
420
|
+
# resolve*, and a link that leads nowhere is a name that does not resolve while the entry it
|
|
421
|
+
# names sits right there in `ls -l`. So the sentence says what the errno knows -- the path led
|
|
422
|
+
# nowhere -- and the three commands whose own reads meet a link now type their own code before
|
|
423
|
+
# they ever reach here.
|
|
424
|
+
errno.ENOENT: SystemRefusal("That path did not lead to anything", "PATH_ABSENT"),
|
|
425
|
+
# `ENOTDIR` establishes only that a component used as a directory is not one; the kernel says
|
|
426
|
+
# a component was used as a
|
|
427
|
+
# directory and is not one, and a pipe, a socket and a device all return it. The sentence
|
|
428
|
+
# reaches `show`, `diff`, `peek`, `author` and `recipe-validate`, so `--output /dev/null/x`
|
|
429
|
+
# and every read through a device called that device a file. It now says what the errno says
|
|
430
|
+
# and nothing more, and `observed` is `None` because nothing was observed.
|
|
431
|
+
errno.ENOTDIR: SystemRefusal("A step of that path is not a folder", "PATH_NOT_A_FILE"),
|
|
432
|
+
errno.EPERM: SystemRefusal("This machine did not allow that", "PERMISSION_DENIED"),
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
# The safety net, and the reason the table above no longer has to be complete to be safe. It used
|
|
436
|
+
# to say that only errnos a command can put in front of someone were listed and that anything else
|
|
437
|
+
# "keeps the operating system's own wording" -- a premise about reachability that was asserted
|
|
438
|
+
# rather than measured, and false both ways round: `ELOOP` and `ENAMETOOLONG` are two commands and
|
|
439
|
+
# one typed path away, and what an unlisted errno kept was not the system's wording but Python's
|
|
440
|
+
# assembly of it, opening with a bracketed number.
|
|
441
|
+
#
|
|
442
|
+
# So an errno with no entry now reads as plainly as one with an entry, and says less rather than
|
|
443
|
+
# saying it in engineering. The exact sentence is one `--json` away, which is the same trade every
|
|
444
|
+
# other translation in this module makes. `FAMILY_HEADLINES` is the same net one layer up: a code
|
|
445
|
+
# nobody has curated still reads plainly because its prefix has a family.
|
|
446
|
+
ANY_SYSTEM_REFUSAL = SystemRefusal("This machine refused that", "SYSTEM_REFUSED")
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
# The strict-JSON refusals are the one class whose first line is unreadable for a reason that has
|
|
450
|
+
# nothing to do with internal words. A `CanonicalJSONError` assembles itself as
|
|
451
|
+
# `<pointer>: <detail> [<code>]`, and the pointer for a whole document is `$` -- so the first thing
|
|
452
|
+
# a person reads out of a terminal is a dollar sign and a colon, which is a shell prompt, not a
|
|
453
|
+
# sentence. The three facts the error carries are right; only the assembly is wrong, so these are
|
|
454
|
+
# rebuilt from those facts rather than substituted for.
|
|
455
|
+
_JSON_SUBJECT = "That file is not JSON the harness can read"
|
|
456
|
+
|
|
457
|
+
# The JSON pointer for the whole document. Any other pointer names a place inside the file, and that
|
|
458
|
+
# is the half of the message worth keeping.
|
|
459
|
+
_WHOLE_DOCUMENT = "$"
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def json_headline(error: BaseException) -> str | None:
|
|
463
|
+
"""The plain first line for a strict-JSON refusal, or ``None`` when it is not one.
|
|
464
|
+
|
|
465
|
+
``None`` for everything else, so the caller can fall through to the other translations. The
|
|
466
|
+
exact assembled string is untouched on the machine-readable object, exactly as it is for every
|
|
467
|
+
other rewriting at this boundary.
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
if not isinstance(error, CanonicalJSONError):
|
|
471
|
+
return None
|
|
472
|
+
pointer = error.path
|
|
473
|
+
if pointer and pointer != _WHOLE_DOCUMENT:
|
|
474
|
+
return f"{_JSON_SUBJECT}: {error.detail}, at {pointer}."
|
|
475
|
+
return f"{_JSON_SUBJECT}: {error.detail}."
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def refused_path(error: BaseException | None) -> str | None:
|
|
479
|
+
"""The path an ``OSError`` names, when it names one that can be printed as a path.
|
|
480
|
+
|
|
481
|
+
``filename`` is ``None`` for a refusal raised on a descriptor and may be an ``int`` file
|
|
482
|
+
descriptor or ``bytes``; neither is a path a person can act on, so both are declined rather
|
|
483
|
+
than rendered. One rule, used by the sentence below and by the command line when it decides
|
|
484
|
+
which path a refusal is about, because two answers to "which path is this" is how a block came
|
|
485
|
+
to name one file and its advice another.
|
|
486
|
+
"""
|
|
487
|
+
|
|
488
|
+
if not isinstance(error, OSError):
|
|
489
|
+
return None
|
|
490
|
+
named = error.filename
|
|
491
|
+
if isinstance(named, str):
|
|
492
|
+
return named
|
|
493
|
+
if isinstance(named, os.PathLike):
|
|
494
|
+
return str(named)
|
|
495
|
+
return None
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def system_refusal(error: BaseException) -> SystemRefusal | None:
|
|
499
|
+
"""What to say and where its fix is written, for a refusal carrying an errno.
|
|
500
|
+
|
|
501
|
+
``None`` for anything with no errno, which is every `OSError` the harness assembles out of its
|
|
502
|
+
own wording without one. It is **not** ``None`` for the few the harness constructs *with* an
|
|
503
|
+
errno -- ``review.py`` and ``pipeline.py`` raise a handful for install-time safety -- and this
|
|
504
|
+
function cannot tell those from the system's own. It therefore treats this table as a
|
|
505
|
+
best-effort translation for both sources.
|
|
506
|
+
"""
|
|
507
|
+
|
|
508
|
+
if not isinstance(error, OSError) or error.errno is None:
|
|
509
|
+
return None
|
|
510
|
+
return SYSTEM_REFUSALS.get(error.errno, ANY_SYSTEM_REFUSAL)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def system_headline(error: BaseException) -> str | None:
|
|
514
|
+
"""The plain first line for a refusal carrying an errno, or ``None``.
|
|
515
|
+
|
|
516
|
+
Total over the errnos: every one gets a sentence, a curated one where there is one and the net
|
|
517
|
+
otherwise, so no line a person reads is ever a bracketed error number.
|
|
518
|
+
"""
|
|
519
|
+
|
|
520
|
+
refusal = system_refusal(error)
|
|
521
|
+
if refusal is None:
|
|
522
|
+
return None
|
|
523
|
+
named = refused_path(error)
|
|
524
|
+
return f"{refusal.headline}: {named}" if named else f"{refusal.headline}."
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def system_lookup_code(error: BaseException) -> str | None:
|
|
528
|
+
"""The remediation key for a refusal that carries no typed code of its own.
|
|
529
|
+
|
|
530
|
+
Never printed as a code and never reaches the machine-readable object, because nothing raises
|
|
531
|
+
it: it exists so an untyped refusal a person can hit still gets plain sentences. It comes off
|
|
532
|
+
the same record as the sentence above, so a refusal that reads plainly always has a fix under
|
|
533
|
+
it and neither half can be added without the other.
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
refusal = system_refusal(error)
|
|
537
|
+
return None if refusal is None else refusal.code
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# What the plain block says about the sentence it replaced, so nothing looks hidden.
|
|
541
|
+
EXACT_WORDING_HINT = "Exact wording: add --json to the same command."
|
|
542
|
+
|
|
543
|
+
# What separates "what went wrong" from "which thing it was about". A colon followed by a space, so
|
|
544
|
+
# a clock time or a Windows drive letter inside a path is never mistaken for that break.
|
|
545
|
+
_SEPARATOR = re.compile(r":\s")
|
|
546
|
+
|
|
547
|
+
# The typed classes that append their own code assemble as `<subject>: <detail> [CODE]`. The plain
|
|
548
|
+
# block already prints the code on a line of its own, so carrying it inside the sentence as well is
|
|
549
|
+
# a duplicate -- and it is a duplicate spelled out of exactly the engineering vocabulary the
|
|
550
|
+
# sentence is being translated out of, so it can put an internal word back on a line the boundary
|
|
551
|
+
# just cleaned.
|
|
552
|
+
_TRAILING_CODE = re.compile(r"\s*\[[A-Z0-9_]+\]\s*$")
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def without_repeated_code(message: str, code: str | None) -> str:
|
|
556
|
+
"""``message`` with its trailing ``[CODE]`` dropped when the block prints that code anyway.
|
|
557
|
+
|
|
558
|
+
Several typed classes assemble themselves as ``<subject>: <detail> [CODE]``. The plain block
|
|
559
|
+
prints ``Code: <CODE>`` on a line of its own two lines below, so the bracketed copy is the same
|
|
560
|
+
fact twice -- and it is the copy spelled in the engineering vocabulary, which is how an internal
|
|
561
|
+
word gets back onto a line that carries none in its wording.
|
|
562
|
+
|
|
563
|
+
Only an exact match for the code being printed is removed, so nothing can be lost here that is
|
|
564
|
+
not already on screen. Removing it is not a translation and is not flagged as one: no wording
|
|
565
|
+
changes, and the machine-readable object is untouched as always.
|
|
566
|
+
"""
|
|
567
|
+
|
|
568
|
+
if not code:
|
|
569
|
+
return message
|
|
570
|
+
return re.sub(rf"\s*\[{re.escape(code)}\]\s*$", "", message)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def _subject_carries_internal_wording(message: str) -> bool:
|
|
574
|
+
"""Whether the thing a message opens with is named with an internal word.
|
|
575
|
+
|
|
576
|
+
A refusal is written ``<subject>: <what went wrong>``, and the subject is usually a field path
|
|
577
|
+
inside a contract record. The rule above deliberately treats a path as a name rather than as
|
|
578
|
+
wording, because `candidate` buried inside `.work/run/candidate/manifest.json` is nobody's
|
|
579
|
+
sentence. That exemption is right in the middle of a line and wrong at the start of one:
|
|
580
|
+
``sandbox.parsed: ...`` puts an internal word in front of a person as the subject of what they
|
|
581
|
+
are about to read. So a subject that carries one is grounds for substituting, even though the
|
|
582
|
+
same token further along the line would not be -- and the exact string is still one flag away.
|
|
583
|
+
"""
|
|
584
|
+
|
|
585
|
+
head = _SEPARATOR.split(message, maxsplit=1)
|
|
586
|
+
if len(head) < 2:
|
|
587
|
+
return False
|
|
588
|
+
return any(pattern.search(head[0]) for pattern in _PATTERNS)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def naming(error: BaseException) -> str:
|
|
592
|
+
"""The class name this refusal's plain sentence is written against.
|
|
593
|
+
|
|
594
|
+
The class hierarchy is followed, so the eight `OSError` subclasses answer through `OSError`:
|
|
595
|
+
what a person is being told is that a filesystem step was refused, not which class carried the
|
|
596
|
+
refusal.
|
|
597
|
+
"""
|
|
598
|
+
|
|
599
|
+
for cls in type(error).__mro__:
|
|
600
|
+
if cls.__name__ in EXCEPTION_HEADLINES:
|
|
601
|
+
return cls.__name__
|
|
602
|
+
return type(error).__name__
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def plain_headline(code: str | None, message: str, *, raised_as: str | None = None) -> str | None:
|
|
606
|
+
"""The plain first line for one failure, or ``None`` when the message is already plain.
|
|
607
|
+
|
|
608
|
+
``None`` rather than the message itself, because the caller has to know whether it substituted:
|
|
609
|
+
a replaced sentence has to say where the exact one went, and an untouched one must not.
|
|
610
|
+
|
|
611
|
+
``code`` is the typed code the error carries; ``raised_as`` is the name of the class that raised
|
|
612
|
+
it, used only for the refusals that carry no code.
|
|
613
|
+
"""
|
|
614
|
+
|
|
615
|
+
if not carries_internal_wording(message) and not _subject_carries_internal_wording(message):
|
|
616
|
+
return None
|
|
617
|
+
plain = HEADLINES.get(code or "")
|
|
618
|
+
if plain is None and code:
|
|
619
|
+
# The compound table corrects a prefix the way remediation's family_for does, so the
|
|
620
|
+
# headline and the remediation paragraph under it always answer as the same family.
|
|
621
|
+
compound = "_".join(code.split("_", 2)[:2])
|
|
622
|
+
family = COMPOUND_PREFIX_FAMILY.get(compound) or PREFIX_FAMILY.get(
|
|
623
|
+
code.split("_", 1)[0], ""
|
|
624
|
+
)
|
|
625
|
+
plain = FAMILY_HEADLINES.get(family)
|
|
626
|
+
if plain is None:
|
|
627
|
+
plain = EXCEPTION_HEADLINES.get(raised_as or "", _LAST_RESORT)
|
|
628
|
+
tail = _plain_tail(message)
|
|
629
|
+
return f"{plain}: {tail}" if tail else f"{plain}."
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def _plain_tail(message: str) -> str | None:
|
|
633
|
+
"""The longest trailing part of ``message``, after a colon, that names something plainly.
|
|
634
|
+
|
|
635
|
+
Most refusals are written ``what went wrong: which thing``. The left half is the part that uses
|
|
636
|
+
the engineering words; the right half is a path, a member name, or a field path, and it is the
|
|
637
|
+
half that says which of a thousand files this is about. Taking the longest clean suffix keeps
|
|
638
|
+
that detail without carrying any of the wording the boundary is here to replace.
|
|
639
|
+
|
|
640
|
+
The class's own trailing ``[CODE]`` is dropped first: the plain block prints the code on its own
|
|
641
|
+
line, so keeping it here would both repeat it and re-introduce an internal word spelled in
|
|
642
|
+
upper case.
|
|
643
|
+
"""
|
|
644
|
+
|
|
645
|
+
trimmed = _TRAILING_CODE.sub("", message)
|
|
646
|
+
for separator in _SEPARATOR.finditer(trimmed):
|
|
647
|
+
tail = trimmed[separator.end() :].strip()
|
|
648
|
+
if tail and not carries_internal_wording(tail) and _TRAILING_CODE.search(tail) is None:
|
|
649
|
+
return tail
|
|
650
|
+
return None
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
__all__ = [
|
|
654
|
+
"ANY_SYSTEM_REFUSAL",
|
|
655
|
+
"EXACT_WORDING_HINT",
|
|
656
|
+
"EXCEPTION_HEADLINES",
|
|
657
|
+
"FAMILY_HEADLINES",
|
|
658
|
+
"HEADLINES",
|
|
659
|
+
"INTERNAL_TERMS",
|
|
660
|
+
"SYSTEM_REFUSALS",
|
|
661
|
+
"SystemRefusal",
|
|
662
|
+
"carries_internal_wording",
|
|
663
|
+
"json_headline",
|
|
664
|
+
"naming",
|
|
665
|
+
"plain_headline",
|
|
666
|
+
"refused_path",
|
|
667
|
+
"system_headline",
|
|
668
|
+
"system_lookup_code",
|
|
669
|
+
"system_refusal",
|
|
670
|
+
"without_repeated_code",
|
|
671
|
+
]
|