mostlyright-data 0.30.5__tar.gz → 0.30.6__tar.gz

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.
Files changed (132) hide show
  1. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/PKG-INFO +1 -1
  2. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +35 -10
  3. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/making-a-table-incremental.md +1 -0
  4. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/recipe-test.md +8 -1
  5. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/recipe.py +250 -26
  6. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/recipe_explain.py +11 -6
  7. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/transport.py +3 -1
  8. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/.gitignore +0 -0
  9. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/README.md +0 -0
  10. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/pyproject.toml +0 -0
  11. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/scripts/hatch_build.py +0 -0
  12. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/SKILL.md +0 -0
  13. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/agents/openai.yaml +0 -0
  14. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +0 -0
  15. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
  16. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
  17. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
  18. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
  19. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
  20. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
  21. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
  22. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/agent-protocol.md +0 -0
  23. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
  24. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
  25. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/boundaries.md +0 -0
  26. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
  27. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/commands.md +0 -0
  28. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
  29. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/installation-parity.md +0 -0
  30. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/live-run.md +0 -0
  31. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/narrating-the-run.md +0 -0
  32. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
  33. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/one-install.md +0 -0
  34. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/patterns.md +0 -0
  35. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/prediction-labels.md +0 -0
  36. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/preparing-table-search.md +0 -0
  37. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/promote.md +0 -0
  38. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/readers.md +0 -0
  39. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/receipts.md +0 -0
  40. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
  41. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
  42. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/reference-pages.md +0 -0
  43. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/required-protocol.md +0 -0
  44. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/source-credentials.md +0 -0
  45. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/sources.md +0 -0
  46. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
  47. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/transforms.md +0 -0
  48. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/user-communication-contract.md +0 -0
  49. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
  50. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  51. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/__init__.py +0 -0
  52. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/agent_protocol.py +0 -0
  53. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/canonical.py +0 -0
  54. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/formats.py +0 -0
  55. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  56. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/key_seam.py +0 -0
  57. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/page_coverage.py +0 -0
  58. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  59. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/session_probes.py +0 -0
  60. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/skill_assets.py +0 -0
  61. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/table_manifest.py +0 -0
  62. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  63. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  64. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  65. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/activity.py +0 -0
  66. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  67. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/backoff.py +0 -0
  68. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/categories.py +0 -0
  69. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/commands.py +0 -0
  70. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  71. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/download.py +0 -0
  72. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
  73. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  74. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/parity.py +0 -0
  75. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/probe.py +0 -0
  76. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
  77. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  78. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/propose.py +0 -0
  79. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  80. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
  81. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
  82. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/recipe_test.py +0 -0
  83. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
  84. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/release_check.py +0 -0
  85. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/research.py +0 -0
  86. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/router.py +0 -0
  87. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
  88. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
  89. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/runs.py +0 -0
  90. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/session.py +0 -0
  91. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/stream.py +0 -0
  92. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  93. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/studio_capabilities.py +0 -0
  94. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/support.py +0 -0
  95. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/table_search_contract.json +0 -0
  96. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  97. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4.py +0 -0
  98. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
  99. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  100. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
  101. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  102. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  103. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
  104. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  105. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  106. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
  107. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  108. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
  109. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
  110. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_search.py +0 -0
  111. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
  112. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  113. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
  114. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
  115. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
  116. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  117. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  118. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  119. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  120. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  121. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  122. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  123. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  124. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  125. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  126. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  127. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  128. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/login.py +0 -0
  129. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  130. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  131. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/remediation.py +0 -0
  132. {mostlyright_data-0.30.5 → mostlyright_data-0.30.6}/src/mostlyright/data_harness/ux/render.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mostlyright-data
3
- Version: 0.30.5
3
+ Version: 0.30.6
4
4
  Summary: Mostly Right hosted CLI for reviewed datasets
5
5
  Project-URL: Homepage, https://mostlyright.md/
6
6
  Project-URL: Documentation, https://mostlyright.md/docs/guides/cli/
@@ -146,19 +146,44 @@ projected to a few columns decodes to a few hundred kilobytes; an 8.6 MB table r
146
146
  to 8.6 MB. The fetched size predicts nothing on its own, and a source over either bound is refused
147
147
  `READER_BUDGET`, which names both.
148
148
 
149
- **How far a source may fetch depends on how it will be read.** A Reader-pinned source, and an
150
- unpinned `csv` one, fetch up to 256 MiB. An unpinned `ndjson` source fetches up to what one
151
- confined request carries, about 36 MB. An unpinned `json` or `parquet` source keeps 16 MiB,
152
- because neither has a row-at-a-time reader and both are read whole inside the clean room —
153
- pinning a Reader is the way past that. What a source may DECODE to is its Reader family's output
154
- budget, narrowed to at most 1 GiB, because the whole decoded relation is held in memory while it
155
- is classified and sealed. A wide source is still best narrowed by projection, which is what
156
- shrinks the decoded relation; a long source can still be split.
149
+ **How far a source may fetch depends on whether it is staged, and then on how it will be read.**
150
+ Studio collects the bytes of a `public.https` source at an `https_url` that names no credential,
151
+ has a `public` classification and states the rights basis `public_domain_asserted`. A Generic API
152
+ or a page collection is not one. Studio writes a collected object to the run's scratch disk and
153
+ decodes it from that file, instead of holding it in memory, when the source is also read whole,
154
+ with no `window`, and is either unpinned or pinned to a Reader with a file decoder. That is a
155
+ staged source. Delimited text and Parquet have a file decoder, and so do the gzip, tar and zip
156
+ archive Readers (not `archive.tar@2.0.0` or `archive.zip@2.0.0`), `meteorology.grib`,
157
+ `meteorology.bufr` and the statistical microdata Readers; `json.tabular` has one in 1.1.0 and
158
+ 1.2.0, and only when it reads `ndjson`. The spreadsheet, XML, HTML, NetCDF and `weather.grib2`
159
+ Readers have none, and a pin to one of them is fetched whole. A staged source is read up to 4 GiB,
160
+ bounded by the `limits.max_source_bytes` you declare, with no other member to set. A sample that
161
+ truncates rows (`--sample` with `--max-rows`) is never staged: a source that would be staged is
162
+ read from memory instead, under the cap below.
163
+
164
+ Every other `public.https` source is held in memory as it is fetched, and a Reader-pinned one, or an
165
+ unpinned `csv` one, fetches up to 256 MiB. An unpinned `ndjson` source fetches up to what one
166
+ confined request carries, about 36 MB. An unpinned `json` or `parquet` source keeps 16 MiB on either
167
+ route, because neither has a row-at-a-time reader and both are read whole inside the clean room —
168
+ pinning a Reader is the way past that. The exception is a source pinned to `parquet.tabular@1.1.0`,
169
+ which fetches by range, up to 1 GiB.
170
+
171
+ The rights basis is a statement about your right to use the source, and it is recorded with the
172
+ run. It is not a size setting: state the one that is true, and let the ceiling follow.
173
+
174
+ What a source that is not staged may DECODE to is its Reader family's output budget, narrowed to at
175
+ most 1 GiB, because the whole decoded relation is held in memory while it is classified and sealed.
176
+ A staged source is decoded to a file and is not narrowed to that 1 GiB. On either route the decoded
177
+ table is also held to the source's own `limits.max_source_bytes`, so when a source inflates, as a
178
+ compressed archive does, declare that at the decoded size and not at the size of the download. A
179
+ wide source is still best narrowed by projection, which is what shrinks the decoded relation; a
180
+ long source can still be split.
157
181
 
158
182
  `limits.max_source_bytes` states where fetching STOPS rather than how big the source is, so the
159
183
  preflight does not refuse a large one. Registration puts one sentence on the receipt under
160
- `warnings` when the declared number is larger than a fetch can reach, and the sentence names the
161
- formats that stop earlier. Read that as "go and check this one", not as a failure.
184
+ `warnings` when the declared number is larger than the route its source takes can reach, and the
185
+ sentence names the route, the ceiling it used and the formats that stop earlier. Read that as "go
186
+ and check this one", not as a failure.
162
187
 
163
188
  **Which remedy applies depends on which way the source is too big, and lowering the ceiling is
164
189
  never it** — that truncates the preview or refuses the full build rather than fetching less. A WIDE
@@ -209,6 +209,7 @@ source and the pointer to change.
209
209
  | `REFRESH_WORKER_UNAVAILABLE` | This Studio has no refresh worker for the plan. | `wait_for_platform`: report "not available on this Studio yet". |
210
210
  | `REBUILD_DISCARDS_SNAPSHOT_HISTORY` | A table declares `rebuild` beside a snapshot source. | `split_table`: keep the snapshot source in a table with no `rebuild`. |
211
211
  | `ROW_REQUEST_ANSWERS_NEVER_EXPIRE` | Advisory. A per-row request names no date parameter and its answers are reused with no bound. | `declare_revisit_interval` or `read_answers_fresh`, both in [Needs Studio 4.14](#needs-studio-414). |
212
+ | `PREDECESSOR_TRUNCATED` | The live version is a `sample` that a row or window clamp cut short, so a refresh would seal over rows it never fetched. Needs Studio 4.33. | `resync_once`: run one full build. |
212
213
 
213
214
  A `permanence` of `predecessor` clears after one full. `estate` is the platform's state, not the
214
215
  recipe's. Report it and do not revise the document for it. `document` needs a revision.
@@ -24,7 +24,14 @@ What it reads:
24
24
  - A per-row source that presents a credential gets no requests unless `--allow-paid` is set. It
25
25
  is reported as skipped, and its relation reaches the transform empty. Its provider's
26
26
  per-request price is the reason, so forecast it and ask the user before adding the flag.
27
- - Generic API walks and whole-file sources are bounded only by the 256 MiB acquisition clamp.
27
+ - Generic API walks and whole-file sources are bounded only by the test's own clamp of 256 MiB
28
+ on source bytes. A whole file larger than that is refused by the test, although the full reads a
29
+ staged source up to 4 GiB
30
+ ([how far a source may fetch](6-build-one-run-sized-to-acquire-every-measured-source-whole.md)).
31
+ `mr-data run --full` runs this test first, so a staged source over 256 MiB stops it at
32
+ `run_stopped_by_test`. That stop comes from the clamp and not from anything wrong in the recipe;
33
+ `--skip-test`, below, is how the user starts that full once they have decided to. A whole file
34
+ that is not staged is refused by the full as well, and `--skip-test` does not help it.
28
35
 
29
36
  Read `status`:
30
37
 
@@ -69,7 +69,7 @@ from mostlyright.data_harness.thin.session import (
69
69
  open_studio_session,
70
70
  sessions_for,
71
71
  )
72
- from mostlyright.data_harness.thin.transport import ThinLaneError
72
+ from mostlyright.data_harness.thin.transport import ThinLaneError, UrlLibTransport
73
73
  from mostlyright.data_harness.thin.v4 import identifier
74
74
  from mostlyright.data_harness.thin.v4_datasets import (
75
75
  StudioV4DatasetClient,
@@ -114,7 +114,9 @@ REQUIRED_DOCUMENT_MEMBERS: tuple[str, ...] = (
114
114
 
115
115
  #: The declared ``limits.max_source_bytes`` above which one source is worth a word, in bytes --
116
116
  #: for a source that is read through descriptors, which is a Reader-pinned one or an unpinned
117
- #: ``csv`` one. That is the hosted fetch cap.
117
+ #: ``csv`` one -- and that is not STAGED (:func:`_is_staged`). That is the hosted whole-read cap,
118
+ #: ``hosted_crawler.MAX_RAW_RESPONSE_BYTES``: the most a fetch that holds the body in memory may
119
+ #: take.
118
120
  READER_SOURCE_BYTE_CEILING = 268_435_456
119
121
 
120
122
  #: The same ceiling for an unpinned ``ndjson`` source. It has an inline streaming pass and no
@@ -127,6 +129,71 @@ NDJSON_SOURCE_BYTE_CEILING = 37_745_664
127
129
  #: Pinning a Reader is what moves a source off this ceiling.
128
130
  DRAINED_SOURCE_BYTE_CEILING = 16_777_216
129
131
 
132
+ #: A source pinned to ``parquet.tabular@1.1.0`` is read by range, and its declared
133
+ #: ``max_source_bytes`` bounds the bytes that read fetches -- the footer and the selected column
134
+ #: chunks -- rather than the object, which may be far larger. Its ceiling is the ranged read's own
135
+ #: (``readers.budgets.MAX_RANGED_READ_BYTES``), not the hosted cap, and the coordinate is
136
+ #: ``readers.parquet_tabular.RANGED_PARQUET_COORDINATE``. Stated here for the reason below.
137
+ RANGED_READ_SOURCE_BYTE_CEILING = 1_073_741_824
138
+ RANGED_READER_COORDINATE = ("parquet.tabular", "1.1.0")
139
+
140
+ #: The same threshold for a STAGED source: one that Studio collects, reads whole and decodes from a
141
+ #: file (:func:`_is_staged`). That object is written to the run's scratch disk instead of being
142
+ #: held, so the whole-read cap does not bind it (Studio #1989, ADR 0099);
143
+ #: ``hosted_crawler.MAX_COLLECTED_FILE_SOURCE_BYTES`` does.
144
+ #:
145
+ #: ⚠ STATED HERE, NOT IMPORTED FROM STUDIO, AND HELD EQUAL TO IT BY A TEST. This client is
146
+ #: standard-library only and the crawler is part of the engine it must never carry, so it cannot
147
+ #: read the ceilings off the module that enforces them. The cross-repository suite
148
+ #: (``tests/test_thin_worker_contract_constants.py``) reads that module's source and fails when
149
+ #: either ceiling here, the ranged one or the table below stops being Studio's. It needs a Studio
150
+ #: checkout and so does not run in CI: run ``make verify-cross-repo`` after a change to any of
151
+ #: them. The sentence that said every Reader-pinned source stops at 256 MiB went on being printed
152
+ #: for days after ADR 0099, because the guard compared the figure with the wrong constant
153
+ #: (mostlyrightmd/mostlyright-planning#1576).
154
+ STAGED_SOURCE_BYTE_CEILING = 4 * 1024**3
155
+
156
+ #: Under this execution policy a staged read has no fixed ceiling: ``limits.max_source_bytes``
157
+ #: bounds it, narrowed by what the run's capacity admits (ADR 0099).
158
+ _BOUNDED_STREAMING_POLICY = "bounded-streaming.v1"
159
+
160
+ #: The Reader coordinates a staged file can be decoded by. A pin to any other is decoded from bytes
161
+ #: held in memory, however the source is collected. Restated from ``acquisition.file_decoding``
162
+ #: (the keys of ``_FILE_FAMILIES`` joined with those of ``_SCRATCH_FILE_FAMILIES``) for the reason
163
+ #: above, and held equal to them by a test in both directions: Studio #2050 added
164
+ #: ``archive.zip@3.0.0`` to the table while the change that first restated it was in review.
165
+ #: ``json.tabular`` has a decoder only for ``ndjson`` (:func:`_reads_ndjson`). Among the certified
166
+ #: coordinates without one are ``spreadsheet.xlsx``, ``xml.tabular``, ``html.web_extract``,
167
+ #: ``scientific.netcdf`` and ``weather.grib2``.
168
+ FILE_DECODER_COORDINATES: frozenset[tuple[str, str]] = frozenset(
169
+ (family, version)
170
+ for family, versions in {
171
+ "archive.gzip": ("1.0.0", "1.1.0", "2.0.0"),
172
+ "archive.tar": ("1.0.0", "1.1.0", "3.0.0"),
173
+ "archive.zip": ("1.0.0", "1.1.0", "1.2.0", "1.3.0", "1.4.0", "1.5.0", "3.0.0"),
174
+ "delimited_text": ("1.0.0", "1.1.0", "2.0.0"),
175
+ "geoparquet.vector": ("1.0.0",),
176
+ "json.tabular": ("1.1.0", "1.2.0"),
177
+ "meteorology.bufr": ("1.0.0",),
178
+ "meteorology.grib": ("1.0.0",),
179
+ "microdata.sas_xpt": ("1.0.0",),
180
+ "microdata.spss_sav": ("1.0.0",),
181
+ "microdata.stata_dta": ("1.0.0",),
182
+ "official.pcaxis_px": ("1.0.0",),
183
+ "parquet.tabular": ("1.0.0", "1.1.0"),
184
+ "raster.geospatial": ("1.0.0",),
185
+ "satellite.himawari_hsd_complete": ("1.0.0",),
186
+ "satellite.msg_seviri_l15": ("1.0.0",),
187
+ "satellite.sentinel_safe": ("1.0.0",),
188
+ "satellite.xrit_image": ("1.0.0",),
189
+ "scientific.fits": ("1.0.0",),
190
+ "scientific.hdf5": ("1.0.0",),
191
+ "scientific.zarr": ("1.0.0",),
192
+ "xbrl.facts": ("1.0.0",),
193
+ }.items()
194
+ for version in versions
195
+ )
196
+
130
197
  #: How many undescribed columns the warning names before it counts the rest. A table plan admits
131
198
  #: 512 columns, and a sentence that spelt every one of them out would be the wall the `numbered`
132
199
  #: receipt member exists to avoid. The receipt carries the whole count as a number beside it, so
@@ -159,10 +226,10 @@ PROPOSE = "propose"
159
226
  EXPLAIN = recipe_explain.EXPLAIN
160
227
  FORECAST = "forecast"
161
228
  TEST = recipe_test.TEST
162
- # Studio evaluates the exact persisted admission proof for each recipe revision. Keep this
163
- # inventory page small enough that a normal production workspace completes each bounded request
164
- # inside the thin client's request deadline, while preserving the established 40,000-item
165
- # inventory bound through cursor aggregation.
229
+ # Studio evaluates the exact persisted admission proof for each recipe revision. A page of 25 is
230
+ # quick on most workspaces; where one revision takes minutes the walk halves the page down to a
231
+ # single row under READINESS_REQUEST_TIMEOUT_SECONDS, at the cost of the timed-out attempts first.
232
+ # Cursor aggregation keeps the established 40,000-item inventory bound.
166
233
  READINESS_PAGE_SIZE = 25
167
234
  MAX_READINESS_ITEMS = 40_000
168
235
  MAX_READINESS_PAGES = MAX_READINESS_ITEMS // READINESS_PAGE_SIZE
@@ -175,6 +242,11 @@ MAX_READINESS_HALVINGS = 5
175
242
  #: (the transport's deadline), or a gateway that gave up waiting for Studio.
176
243
  READINESS_TIMEOUT_STATUSES = frozenset({502, 503, 504})
177
244
 
245
+ #: How long one readiness page may take. Studio evaluates each revision's persisted admission
246
+ #: proof, and on a large production workspace a single revision has taken about 110 seconds, past
247
+ #: the thirty seconds an ordinary request gets: no page, however halved, could come back.
248
+ READINESS_REQUEST_TIMEOUT_SECONDS = 300.0
249
+
178
250
  #: How long the walk waits before it asks a page again after one of those statuses, doubling to the
179
251
  #: ceiling, each wait jittered over the upper half of its step. A gateway that answered at once
180
252
  #: names an overloaded Studio, and every client in the walk asking straight back adds to the load.
@@ -419,7 +491,7 @@ def check_document(document: dict[str, Any]) -> None:
419
491
  check_dataset_binding(document["dataset"])
420
492
 
421
493
 
422
- def source_byte_warnings(sources: Any) -> list[str]:
494
+ def source_byte_warnings(sources: Any, *, execution_policy: Any = None) -> list[str]:
423
495
  """One sentence per source whose declared ceiling is larger than the hosted lane fetches.
424
496
 
425
497
  ⚠ WARNINGS, AND NOT A GATE, FOR THE REASON :data:`READER_SOURCE_BYTE_CEILING` GIVES: the
@@ -428,6 +500,18 @@ def source_byte_warnings(sources: Any) -> list[str]:
428
500
  decides anything; it tells the agent that wrote the number that the hosted lane will not honour
429
501
  it.
430
502
 
503
+ ⚠ "THE HOSTED LANE" IS SEVERAL ROUTES, AND A SOURCE IS HELD TO THE CEILING OF THE ONE IT TAKES.
504
+ A staged source is written to disk and read to :data:`STAGED_SOURCE_BYTE_CEILING`, a ranged
505
+ Parquet read has its own, and every other source is held to
506
+ :data:`READER_SOURCE_BYTE_CEILING` (:func:`_source_byte_ceiling`). Telling a staged source that
507
+ its fetch stops at 256 MiB sent authors off to shrink sources that read in full
508
+ (mostlyrightmd/mostlyright-planning#1574), and this client's build skill said the same
509
+ (mostlyrightmd/mostlyright-planning#1576).
510
+
511
+ ⚠ UNDER ``bounded-streaming.v1`` A STAGED READ HAS NO CEILING TO OVERSTATE, so it is not
512
+ warned about: the declared number alone bounds it. That policy is a member of the document, not
513
+ of a source, which is why it is passed in beside the sources.
514
+
431
515
  ⚠ THE LARGEST SOURCE DECIDES, NOT THE SUM. One source is fetched at a time, so the sentence is
432
516
  about the one that overstates its ceiling rather than about the total.
433
517
 
@@ -448,7 +532,10 @@ def source_byte_warnings(sources: Any) -> list[str]:
448
532
  declared = limits.get("max_source_bytes")
449
533
  if type(declared) is not int:
450
534
  continue
451
- ceiling, why = _source_byte_ceiling(source)
535
+ held = _source_byte_ceiling(source, execution_policy=execution_policy)
536
+ if held is None:
537
+ continue
538
+ ceiling, why, sooner = held
452
539
  if declared <= ceiling:
453
540
  continue
454
541
  named = source.get("name")
@@ -457,10 +544,7 @@ def source_byte_warnings(sources: Any) -> list[str]:
457
544
  f"source {where} declares limits.max_source_bytes of {declared:,} bytes, and {why} "
458
545
  f"stops at {ceiling:,}. That is a ceiling on fetching rather than a measurement of "
459
546
  "the source, so this registers and runs, and the fetch stops there whatever the "
460
- "document says. An unpinned source that answers ndjson stops at "
461
- f"{NDJSON_SOURCE_BYTE_CEILING:,} and one that answers json or parquet at "
462
- f"{DRAINED_SOURCE_BYTE_CEILING:,}, and pinning a Reader is what moves it off "
463
- "those. Declare what the source actually weighs"
547
+ f"document says. {sooner}. Declare what the source actually weighs"
464
548
  )
465
549
  return said
466
550
 
@@ -1013,24 +1097,156 @@ def sample_span_warnings(sources: Any) -> list[str]:
1013
1097
  return [". ".join(said)]
1014
1098
 
1015
1099
 
1016
- def _source_byte_ceiling(source: Mapping[str, Any]) -> tuple[int, str]:
1017
- """The fetch ceiling one declared source is held to, and the phrase that names it.
1100
+ def _stated_parameters(source: Mapping[str, Any]) -> dict[str, Any]:
1101
+ """The ``connector.parameters`` one source states, by name.
1018
1102
 
1019
- A document declares a Reader pin on ``connector.parameters`` and declares no wire format at
1020
- all: the format is what the response turns out to be. So the one thing this client can read
1021
- off the document is whether the source is pinned. A pinned source fetches to the hosted cap
1022
- whatever its wire format. An unpinned source is held to the same cap here, and the sentence
1023
- says which formats stop earlier, because that is decided by bytes this client has not seen.
1103
+ A parameter whose name is not text names nothing, so it is left out and never hashed: the
1104
+ schema refuses it and the server says so, and this runs before the server has been asked
1105
+ (``--no-lint`` lets a document that fails the schema reach it).
1024
1106
  """
1025
1107
 
1026
1108
  connector = source.get("connector")
1027
1109
  parameters = connector.get("parameters") if isinstance(connector, Mapping) else None
1028
- if isinstance(parameters, list) and any(
1029
- isinstance(parameter, Mapping) and parameter.get("name") == "reader.family_id"
1110
+ if not isinstance(parameters, list):
1111
+ return {}
1112
+ return {
1113
+ parameter["name"]: parameter.get("value")
1030
1114
  for parameter in parameters
1031
- ):
1032
- return READER_SOURCE_BYTE_CEILING, "a Reader-pinned source's fetch"
1033
- return READER_SOURCE_BYTE_CEILING, "an unpinned source's fetch"
1115
+ if isinstance(parameter, Mapping) and isinstance(parameter.get("name"), str)
1116
+ }
1117
+
1118
+
1119
+ def _is_collected(source: Mapping[str, Any]) -> bool:
1120
+ """Whether Studio's shared fetch lane collects this source's bytes.
1121
+
1122
+ Five members of the document decide it: the adapter is exactly ``public.https``, the connector
1123
+ names no credential, the locator is an https URL, the classification is ``public`` and the
1124
+ rights basis is ``public_domain_asserted``. The worker reads these five
1125
+ (``hosted_run_worker._uses_collected_source_fetch``, beside a ``window`` and what the job
1126
+ delegated) and so does Studio's route matrix. A ``public.https`` source that fails one of them
1127
+ is not collected. Collected is not staged: that is :func:`_is_staged`'s question.
1128
+
1129
+ ⚠ THE FIVE ARE HELD TO THE WORKER'S, not merely to this docstring:
1130
+ ``tests/test_thin_worker_contract_constants.py`` reads the members that function compares and
1131
+ fails when they change.
1132
+ """
1133
+
1134
+ connector = source.get("connector")
1135
+ locator = source.get("locator")
1136
+ rights = source.get("rights_claim")
1137
+ return (
1138
+ isinstance(connector, Mapping)
1139
+ and connector.get("adapter_id") == "public.https"
1140
+ and connector.get("credential_mode") == "none"
1141
+ and isinstance(locator, Mapping)
1142
+ and locator.get("kind") == "https_url"
1143
+ and source.get("data_classification") == "public"
1144
+ and isinstance(rights, Mapping)
1145
+ and rights.get("claimed_basis") == "public_domain_asserted"
1146
+ )
1147
+
1148
+
1149
+ def _reads_ndjson(options: Any) -> bool:
1150
+ """Whether a ``reader.decode_options`` value, a JSON text, says ``document_format: "ndjson"``.
1151
+
1152
+ ``json.tabular`` has a file decoder only for newline-delimited documents
1153
+ (``acquisition.file_decoding.file_decode_supported``), and its ``document_format`` defaults to
1154
+ ``json``. Text that is not canonical JSON, or not an object, reads as not ndjson: the server
1155
+ validates the options, and this only decides which ceiling to name.
1156
+
1157
+ ⚠ IT MUST NOT RAISE, because it runs before the server has been asked and the preflight lets
1158
+ a long, hostile-looking pin through: the schema admits 262,144 characters. The parser refuses
1159
+ most bad text with its own error, but text nested a few thousand levels deep escapes it as a
1160
+ ``RecursionError`` and an integer of more than 4,300 digits as a ``ValueError``, and either
1161
+ would turn a registration into ``THIN_COMMAND_FAILED`` before Studio could say what is wrong.
1162
+ """
1163
+
1164
+ if not isinstance(options, str):
1165
+ return False
1166
+ try:
1167
+ stated = parse_json(options)
1168
+ except (RecursionError, ValueError):
1169
+ # `CanonicalJSONError` is a `ValueError`, so this is the parser's own refusal as well.
1170
+ return False
1171
+ return isinstance(stated, Mapping) and stated.get("document_format") == "ndjson"
1172
+
1173
+
1174
+ def _is_staged(source: Mapping[str, Any], stated: Mapping[Any, Any]) -> bool:
1175
+ """Whether a run reads this source from a file staged on the scratch disk, not from memory.
1176
+
1177
+ ADR 0099 stages a COLLECTED object (:func:`_is_collected`) and decodes it from that file, on
1178
+ two more conditions the document states. The source is read whole, so it has no ``window``
1179
+ member: a windowed read is fetched a window at a time. And it is either unpinned -- ``csv`` and
1180
+ ``ndjson`` stage -- or pinned to a Reader with a file decoder (:data:`FILE_DECODER_COORDINATES`,
1181
+ and ``json.tabular`` only when it reads ``ndjson``). A pin to any other Reader is decoded from
1182
+ bytes held in memory, however the source is collected. The table also holds the ranged Parquet
1183
+ coordinate; :func:`_source_byte_ceiling` has already given that pin its own ceiling.
1184
+
1185
+ ⚠ A DOCUMENT-LEVEL ANSWER, FOR THE FULL READ. What a run holds is not in the document: a
1186
+ scratch disk, and a truncating row clamp (a sample), which is read whole and never staged. A
1187
+ source this calls staged is staged by a full run, and that is the read the ceiling is warned
1188
+ against.
1189
+ """
1190
+
1191
+ if not _is_collected(source) or source.get("window") is not None:
1192
+ return False
1193
+ if "reader.family_id" not in stated:
1194
+ return True
1195
+ family = stated.get("reader.family_id")
1196
+ version = stated.get("reader.family_version")
1197
+ # A value the schema refuses (a list, a number) is not a coordinate: it must not reach the
1198
+ # table, where an unhashable one would raise before the server could answer with its 422.
1199
+ if not isinstance(family, str) or not isinstance(version, str):
1200
+ return False
1201
+ if (family, version) not in FILE_DECODER_COORDINATES:
1202
+ return False
1203
+ return family != "json.tabular" or _reads_ndjson(stated.get("reader.decode_options"))
1204
+
1205
+
1206
+ def _source_byte_ceiling(
1207
+ source: Mapping[str, Any], *, execution_policy: Any = None
1208
+ ) -> tuple[int, str, str] | None:
1209
+ """Where fetching one declared source stops, the phrase that names it, and what stops sooner.
1210
+
1211
+ ``None`` when nothing but the declared number does: a staged read under
1212
+ ``bounded-streaming.v1``.
1213
+
1214
+ A document declares a Reader pin on ``connector.parameters`` and declares no wire format at
1215
+ all: the format is what the response turns out to be. So what this client reads off the
1216
+ document is the ROUTE a source takes, and whether it is pinned, and to what. A ranged Parquet
1217
+ pin bounds the bytes its ranged read fetches, at the ranged ceiling; that read is never
1218
+ staged, so it is checked first. A staged source is read to the staged ceiling. Every other
1219
+ source is held to the whole-read cap here, pinned or not, and the sentence says which unpinned
1220
+ formats stop earlier, because that is decided by bytes this client has not seen.
1221
+
1222
+ The third member is that sentence, because which formats stop sooner depends on the route: a
1223
+ staged unpinned ``csv`` or ``ndjson`` source is read from the file, so only ``json`` and
1224
+ ``parquet`` stop early; fetched whole, ``ndjson`` stops early as well.
1225
+ """
1226
+
1227
+ stated = _stated_parameters(source)
1228
+ whole_read_sooner = (
1229
+ f"An unpinned source that answers ndjson stops at {NDJSON_SOURCE_BYTE_CEILING:,} and one "
1230
+ f"that answers json or parquet at {DRAINED_SOURCE_BYTE_CEILING:,}, and pinning a Reader "
1231
+ "is what moves it off those"
1232
+ )
1233
+ if (
1234
+ stated.get("reader.family_id"),
1235
+ stated.get("reader.family_version"),
1236
+ ) == RANGED_READER_COORDINATE:
1237
+ return RANGED_READ_SOURCE_BYTE_CEILING, "a ranged Parquet read", whole_read_sooner
1238
+ if _is_staged(source, stated):
1239
+ if execution_policy == _BOUNDED_STREAMING_POLICY:
1240
+ return None
1241
+ return (
1242
+ STAGED_SOURCE_BYTE_CEILING,
1243
+ "a collected source's staged read",
1244
+ "An unpinned source that answers json or parquet stops at "
1245
+ f"{DRAINED_SOURCE_BYTE_CEILING:,}, and pinning a Reader is what moves it off that",
1246
+ )
1247
+ if "reader.family_id" in stated:
1248
+ return READER_SOURCE_BYTE_CEILING, "a Reader-pinned source's fetch", whole_read_sooner
1249
+ return READER_SOURCE_BYTE_CEILING, "an unpinned source's fetch", whole_read_sooner
1034
1250
 
1035
1251
 
1036
1252
  def columns_without_description(table: Any) -> list[str]:
@@ -1253,7 +1469,9 @@ def register(
1253
1469
  undescribed = columns_without_description(document.get("table"))
1254
1470
  uncontinued = sources_without_continuation(document.get("sources"))
1255
1471
  declared_sources = document.get("sources")
1256
- warnings = source_byte_warnings(declared_sources)
1472
+ warnings = source_byte_warnings(
1473
+ declared_sources, execution_policy=document.get("execution_policy")
1474
+ )
1257
1475
  warnings += continuation_warnings(
1258
1476
  uncontinued, len(declared_sources) if isinstance(declared_sources, list) else 0
1259
1477
  )
@@ -1614,7 +1832,9 @@ def readiness(
1614
1832
  recipe_id = identifier(recipe_id, "recipe identifier")
1615
1833
  if client is None:
1616
1834
  sessions = sessions or open_renewable_session(resolve_cloud_credentials())
1617
- selected = StudioV4DatasetClient(sessions.session)
1835
+ selected = StudioV4DatasetClient(
1836
+ sessions.session, transport=UrlLibTransport(timeout=READINESS_REQUEST_TIMEOUT_SECONDS)
1837
+ )
1618
1838
  else:
1619
1839
  selected = client
1620
1840
  sessions = sessions or sessions_for(selected.session)
@@ -2015,6 +2235,7 @@ __all__ = [
2015
2235
  "COMMAND_HELP",
2016
2236
  "DRAINED_SOURCE_BYTE_CEILING",
2017
2237
  "EXPLAIN",
2238
+ "FILE_DECODER_COORDINATES",
2018
2239
  "FIRST_READINESS_RETRY_SECONDS",
2019
2240
  "FORECAST",
2020
2241
  "MAX_DOCUMENT_BYTES",
@@ -2028,6 +2249,8 @@ __all__ = [
2028
2249
  "NDJSON_SOURCE_BYTE_CEILING",
2029
2250
  "PROPOSE",
2030
2251
  "PROPOSE_SCHEMA",
2252
+ "RANGED_READER_COORDINATE",
2253
+ "RANGED_READ_SOURCE_BYTE_CEILING",
2031
2254
  "READER_SOURCE_BYTE_CEILING",
2032
2255
  "READINESS",
2033
2256
  "READINESS_FILTERS_SINCE",
@@ -2037,6 +2260,7 @@ __all__ = [
2037
2260
  "REQUIRED_DOCUMENT_MEMBERS",
2038
2261
  "SHOW",
2039
2262
  "SHOW_SCHEMA",
2263
+ "STAGED_SOURCE_BYTE_CEILING",
2040
2264
  "TEST",
2041
2265
  "UNREFRESHABLE_PLAN_TAIL",
2042
2266
  "SampleSpan",
@@ -81,6 +81,9 @@ RECORDING_CONTINUATION_PENDING = "RECORDING_CONTINUATION_PENDING"
81
81
  REFRESH_WORKER_UNAVAILABLE = "REFRESH_WORKER_UNAVAILABLE"
82
82
  REBUILD_DISCARDS_SNAPSHOT_HISTORY = "REBUILD_DISCARDS_SNAPSHOT_HISTORY"
83
83
  ROW_REQUEST_ANSWERS_NEVER_EXPIRE = "ROW_REQUEST_ANSWERS_NEVER_EXPIRE"
84
+ #: Contract 4.33.0 (planning#1337): a refresh whose predecessor is a sample a row or window clamp
85
+ #: cut short. It needs a sealed predecessor, so the mirror never claims it.
86
+ PREDECESSOR_TRUNCATED = "PREDECESSOR_TRUNCATED"
84
87
 
85
88
  RULE_CODES: tuple[str, ...] = (
86
89
  NO_SEALED_PREDECESSOR,
@@ -98,10 +101,11 @@ RULE_CODES: tuple[str, ...] = (
98
101
  REFRESH_WORKER_UNAVAILABLE,
99
102
  REBUILD_DISCARDS_SNAPSHOT_HISTORY,
100
103
  ROW_REQUEST_ANSWERS_NEVER_EXPIRE,
104
+ PREDECESSOR_TRUNCATED,
101
105
  )
102
106
 
103
107
  #: The codes the mirror can observe from a document alone, and so the codes a disagreement is
104
- #: judged over. The other six need a predecessor or the deployment (a sealed version, a table
108
+ #: judged over. The other seven need a predecessor or the deployment (a sealed version, a table
105
109
  #: format, a certified worker, a recording bookmark), and the mirror never claims them.
106
110
  #: ``REFRESH_WORKER_UNAVAILABLE`` is judged only where it names sources: the snapshot fence, which
107
111
  #: ``capabilities.snapshot_refresh`` states; the certified-worker case names none.
@@ -1249,8 +1253,11 @@ def explain_document_at_studio(
1249
1253
  except ThinLaneError as refusal:
1250
1254
  if route_unavailable(refusal):
1251
1255
  return None
1252
- keyless = [pointer for pointer, capability in gated_grammar(document)
1253
- if capability == "keyless_window"]
1256
+ keyless = [
1257
+ pointer
1258
+ for pointer, capability in gated_grammar(document)
1259
+ if capability == "keyless_window"
1260
+ ]
1254
1261
  if keyless and getattr(refusal, "http_status", None) == 422:
1255
1262
  # Older API schemas reject the document before they can answer capabilities.
1256
1263
  # A schema rejection is never evidence that this grammar is served.
@@ -1393,9 +1400,7 @@ def registration_gate(client: StudioV4Client, document: Mapping[str, Any]) -> li
1393
1400
  return grammar_gate(document, answer)
1394
1401
 
1395
1402
 
1396
- def grammar_gate(
1397
- document: Mapping[str, Any], answer: Mapping[str, Any] | None
1398
- ) -> list[str]:
1403
+ def grammar_gate(document: Mapping[str, Any], answer: Mapping[str, Any] | None) -> list[str]:
1399
1404
  """Admit only grammar explicitly served by an explanation; perform no writes."""
1400
1405
 
1401
1406
  gated = gated_grammar(document)
@@ -105,7 +105,9 @@ class UrlLibTransport:
105
105
  under a two-second clock for the same reason. Two clients pass a LONGER one, because each calls
106
106
  a Cloud route that waits on Studio before it answers: ``v4_public.PublicQueryClient`` (a public
107
107
  query) and ``v4_artifacts.CloudArtifactSessionClient`` (the metered signed-read mints, where
108
- Cloud allows Studio sixty seconds). Nothing else passes it.
108
+ Cloud allows Studio sixty seconds), and ``recipe readiness`` waits five minutes for a page
109
+ Studio evaluates revision by revision. Others pass their own clocks too; read each caller's
110
+ constant.
109
111
 
110
112
  ⚠ AND IT MAY BE A CALLABLE, WHICH IS THE SAME PARAMETER SAYING A DIFFERENT KIND OF THING. A
111
113
  constant bounds each request separately, so N requests cost N times the clock; a callable is