mostlyright-data 0.19.1__tar.gz → 0.19.2__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 (72) hide show
  1. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/PKG-INFO +7 -5
  2. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/README.md +6 -4
  3. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/pyproject.toml +1 -1
  4. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/skills/mr-data-build/SKILL.md +236 -133
  5. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/skills/mr-data-build/agents/openai.yaml +1 -1
  6. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/.gitignore +0 -0
  7. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/scripts/hatch_build.py +0 -0
  8. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  9. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/__init__.py +0 -0
  10. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/canonical.py +0 -0
  11. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/formats.py +0 -0
  12. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  13. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/key_seam.py +0 -0
  14. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/page_coverage.py +0 -0
  15. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  16. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/session_probes.py +0 -0
  17. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/skill_assets.py +0 -0
  18. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/table_manifest.py +0 -0
  19. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  20. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  21. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  22. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/activity.py +0 -0
  23. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  24. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/categories.py +0 -0
  25. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/commands.py +0 -0
  26. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  27. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/download.py +0 -0
  28. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  29. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/parity.py +0 -0
  30. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/probe.py +0 -0
  31. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  32. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/propose.py +0 -0
  33. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/recipe.py +0 -0
  34. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/research.py +0 -0
  35. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/router.py +0 -0
  36. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/runs.py +0 -0
  37. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/session.py +0 -0
  38. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/stream.py +0 -0
  39. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  40. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/transport.py +0 -0
  41. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  42. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4.py +0 -0
  43. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
  44. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  45. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  46. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  47. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
  48. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  49. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  50. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  51. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
  52. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  53. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
  54. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
  55. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
  56. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  57. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  58. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  59. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  60. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  61. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  62. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  63. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  64. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  65. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  66. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  67. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  68. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/login.py +0 -0
  69. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  70. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  71. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/src/mostlyright/data_harness/ux/remediation.py +0 -0
  72. {mostlyright_data-0.19.1 → mostlyright_data-0.19.2}/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.19.1
3
+ Version: 0.19.2
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/
@@ -111,10 +111,12 @@ worker executables or image publisher.
111
111
 
112
112
  ## Documentation
113
113
 
114
- - [CLI guide](https://mostlyright.md/docs/guides/cli/)
115
- - [Hosted research sessions](https://mostlyright.md/docs/guides/hosted-research/)
116
- - [Credentials](https://mostlyright.md/docs/guides/credentials/)
117
- - [Public datasets](https://mostlyright.md/docs/guides/use-public-datasets/)
114
+ - [Install and sign in](https://mostlyright.md/docs/start/install/)
115
+ - [Build your first dataset](https://mostlyright.md/docs/start/first-dataset/)
116
+ - [CLI reference](https://mostlyright.md/docs/reference/cli/)
117
+ - [Recipe reference](https://mostlyright.md/docs/reference/recipe/)
118
+ - [Recipe examples](https://mostlyright.md/docs/recipes/)
119
+ - [Use a public dataset](https://mostlyright.md/docs/guides/use-public-datasets/)
118
120
  - [Certified document extraction](docs/DOCUMENT-EXTRACTION.md)
119
121
 
120
122
  Use `mr-data --help` for the full command list and options. Commands that support
@@ -99,10 +99,12 @@ worker executables or image publisher.
99
99
 
100
100
  ## Documentation
101
101
 
102
- - [CLI guide](https://mostlyright.md/docs/guides/cli/)
103
- - [Hosted research sessions](https://mostlyright.md/docs/guides/hosted-research/)
104
- - [Credentials](https://mostlyright.md/docs/guides/credentials/)
105
- - [Public datasets](https://mostlyright.md/docs/guides/use-public-datasets/)
102
+ - [Install and sign in](https://mostlyright.md/docs/start/install/)
103
+ - [Build your first dataset](https://mostlyright.md/docs/start/first-dataset/)
104
+ - [CLI reference](https://mostlyright.md/docs/reference/cli/)
105
+ - [Recipe reference](https://mostlyright.md/docs/reference/recipe/)
106
+ - [Recipe examples](https://mostlyright.md/docs/recipes/)
107
+ - [Use a public dataset](https://mostlyright.md/docs/guides/use-public-datasets/)
106
108
  - [Certified document extraction](docs/DOCUMENT-EXTRACTION.md)
107
109
 
108
110
  Use `mr-data --help` for the full command list and options. Commands that support
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mostlyright-data"
3
- version = "0.19.1"
3
+ version = "0.19.2"
4
4
  description = "Mostly Right hosted CLI for reviewed datasets"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mr-data-build
3
- description: Build and verify a reproducible Parquet dataset from a data question and supported sources on the hosted Mostly Right backend. Use whenever a user asks to use, test, smoke-test, demonstrate, or debug the Mostly Right Harness or mr-data CLI by producing, reviewing, or verifying a dataset.
3
+ description: Build and verify a reproducible Parquet dataset from a data question and supported sources on the hosted Mostly Right backend. Use whenever a user asks to use, test, smoke-test, demonstrate, or debug the Mostly Right Harness or mr-data CLI by producing, reviewing, or verifying a dataset. The published reference at https://mostlyright.md/docs/ is the contract.
4
4
  ---
5
5
 
6
6
  # Mostly Right Data Build
@@ -81,16 +81,49 @@ work, follow it, and read what the backend proved. Studio is the hosted release
81
81
 
82
82
  The product is hosted-only and the client is thin. The engine runs on the backend; `mr-data` is a
83
83
  pure-Python client that submits work and reads the records Studio holds.
84
- [Hosted parity](../../docs/HOSTED-PARITY.md) is the ledger of which name runs where.
84
+ [Install and sign in](https://mostlyright.md/docs/start/install/) states that one install and where
85
+ a command's work actually runs.
85
86
 
86
87
  Read this document in two parts. Everything except [Not hosted yet](#not-hosted-yet) uses only
87
88
  commands this product runs. [Not hosted yet](#not-hosted-yet) is the internal capability map for
88
89
  the one command with no implementation behind it.
89
90
 
90
- Every `../../docs/NAME.md` below names a file in the `mostlyrightmd/mostlyright-data-harness`
91
- repository. This document is installed on its own, so those are references and not links you can
92
- follow from here: everything you need to run a build is in this file, and each of them is the
93
- longer reference behind one section of it.
91
+ Every link below is a page of the published reference at https://mostlyright.md/docs/, and every
92
+ one of them can be fetched from here. This document is installed on its own and remains the whole
93
+ of what a build needs; each page is the longer, current reference behind one section of it.
94
+
95
+ ## Reference pages
96
+
97
+ The published reference at https://mostlyright.md/docs/ carries the current contract for every
98
+ field, flag, code and ceiling this document names. Fetch the page for the shape in front of you
99
+ BEFORE drafting it, never guess a field name, and where a page disagrees with what you remember of
100
+ an older version, the page is right.
101
+
102
+ | What you are about to do | Fetch |
103
+ | --- | --- |
104
+ | Write a recipe document | https://mostlyright.md/docs/reference/recipe/ |
105
+ | Declare a check | https://mostlyright.md/docs/reference/checks/ |
106
+ | Plan a transform step | https://mostlyright.md/docs/reference/transforms/ |
107
+ | Name a unit | https://mostlyright.md/docs/reference/units/ |
108
+ | Choose a connector, a source kind or a connector parameter | https://mostlyright.md/docs/reference/sources/ |
109
+ | Pin a Reader | https://mostlyright.md/docs/reference/readers/ |
110
+ | Read any command's flags and `--json` shape | https://mostlyright.md/docs/reference/cli/ , then the group page: `auth`, `dataset`, `recipe`, `run`, `read`, `table`, `stream` under `/docs/reference/cli/` |
111
+ | Read an error code, a refusal or a run state | https://mostlyright.md/docs/reference/run-states-and-errors/ |
112
+ | Check a ceiling before starting a run | https://mostlyright.md/docs/reference/limits/ |
113
+ | Follow the whole lifecycle end to end | https://mostlyright.md/docs/start/how-it-works/ |
114
+ | Install the client and sign in | https://mostlyright.md/docs/start/install/ |
115
+
116
+ Read the worked recipe closest to the source in hand before writing your own.
117
+ https://mostlyright.md/docs/recipes/ lists all eight: `city-temperatures` (daily city
118
+ temperatures), `csv-snapshot` (one CSV file, one table), `json-api` (records pointer and
119
+ pagination), `html-collection` (many HTML pages, one table), `document-extraction` (PDF
120
+ projection), `weather-grib` (GRIB2 and the scientific Readers), `websocket-stream` (a `wss://`
121
+ venue), `multi-source-join` (two sources joined, with checks).
122
+
123
+ The build guides run one per stage of the loop below, each at https://mostlyright.md/docs/build/NAME/
124
+ where NAME is `probe-sources`, `write-a-recipe`, `run-and-inspect`, `publish-and-refresh`,
125
+ `credentials`, `live-streams`, `collections`, `documents` or `troubleshooting`; the list itself is
126
+ at https://mostlyright.md/docs/build/ and the reference index at https://mostlyright.md/docs/reference/.
94
127
 
95
128
  ## One install
96
129
 
@@ -117,7 +150,7 @@ gap rather than doing anything, and `export-hosted-candidate`, which is a backen
117
150
  | `mr-data login` | Complete device approval and store a device credential. `login --force` is the compatibility alias for safe `auth rotate`, never an in-place truncate. |
118
151
  | `mr-data whoami` | Use the compatibility alias for the remotely validated `auth status` answer. |
119
152
  | `mr-data clarify` | Say whether one clarifying question may be put to the user right now, and compose the record of their answer. It reaches nothing, asks nobody and waits for nothing. Exits non-zero when no person can answer or when a recipe is already registered. |
120
- | `mr-data probe` | Ask one registered source one question and print the answer. **Currently unusable:** it reaches `/v3/sessions`, which Studio refuses, and it takes a question identifier that no v4 receipt returns. Do not plan a source inspection around it. |
153
+ | `mr-data probe` | `probe SOURCE_ID QUESTION_ID --dataset DATASET_ID [--kind source_inspect\|sample_rows\|profile_columns\|evaluate_expression]` asks one already-registered source one question and prints the answer. **Do not plan a source inspection around it.** Both positionals are identifiers — `QUESTION_ID` is a question identifier, which no v4 registration receipt returns — and the command rides the frozen `/v3/sessions` routes, which a deployment may have switched off. Read a source instead by registering the recipe and taking an unwindowed sample under `--max-rows`, then `peek`, `query` and `receipt`. |
121
154
  | `mr-data catalog` | `catalog search "QUESTION"` asks the sealed public-source catalogue which of its entries might answer a question and ranks them best first. Every ranked entry comes back with the disposition its own facts earned — `admitted`, `human_escalation_required` or `refused` — because an entry the catalogue could not vouch for is still a finding. `--format csv` states the one data format the question requires — one token per question, never repeated — and it is not a filter: an entry that does not declare that format still comes back ranked, with disposition `refused` and `filters_match` false, so nothing is held back; `--limit N` ranks at most N, up to 25. It fetches nothing and registers nothing. The catalogue holds one provider (Data.gov) and only part of it, so it is never exhaustive and an empty answer is not evidence that no such source exists. When the answer is `catalog_unavailable`, this deployment has no catalogue to search: record the lane and carry on. |
122
155
  | `mr-data dataset` | Bring the dataset page into existence before there is anything on it, then fill it in while somebody watches. `dataset create --name TEXT` mints it and prints the `dataset_id`; `dataset show ID` reads it back; `dataset set ID --name TEXT --topics "a,b,c" --license ID --description-file F` writes the title, the descriptive tags, the SPDX licence and the description under the version it was read at, retrying once if somebody else wrote first, and an empty `--topics` or `--license` takes that value off the page; `dataset note ID --heading H --blocks-file B` writes one cell of the decision record that OUTLIVES every run, and `--list` reads it back; `dataset watch ID` follows the page's own event stream; `dataset activity ID --phase P --message TEXT` says what is happening right now, silently, and is never a chat message and never a cell; `dataset publish ID [--mode public|link|private]` says who can read the dataset — `public` lists it in the public directory and serves it at an address anybody can read, `link` serves it at an unlisted address, `private` takes it back to the workspace — and `dataset publish ID --show` reads that back without changing it; `dataset archive ID --confirm-name TITLE` retires the page and frees its title, deleting nothing. |
123
156
  | `mr-data recipe` | Register one recipe document — dataset, question, table plan, sources, transform, checks and units — in one call, and print the identifiers the server derived. `mr-data recipe show ID` reads one back. |
@@ -160,7 +193,7 @@ step between a run finishing and reading it: `download`, `receipt`, `checks`, `p
160
193
  answer about a run that is still workspace scratch exactly as they answer about one somebody has
161
194
  since made live.
162
195
 
163
- [The recipe document](../../docs/RECIPE-DOCUMENT.md) is the reference for what
196
+ [The recipe document](https://mostlyright.md/docs/reference/recipe/) is the reference for what
164
197
  `mr-data recipe FILE` sends. Two rules in it are worth knowing before you write one: no fractional
165
198
  number appears anywhere in the document, and you neither compute nor state a digest — the server
166
199
  canonicalizes the document, digests it, and answers with every identifier it derived. Sending the
@@ -172,8 +205,9 @@ signed something on a person's own computer, or they were named for a step the p
172
205
  performs — asking a person before the work starts, reading back a separate body of evidence,
173
206
  opening a session before a question could be asked. For a while each still typed and answered with
174
207
  where its work went; that ended when the refusing names outnumbered the working ones two to one.
175
- [Hosted parity](../../docs/HOSTED-PARITY.md) lists every one of them and what replaced it, and is
176
- the only place they are still written down.
208
+ [Install and sign in](https://mostlyright.md/docs/start/install/) states the one install and the
209
+ whole surface it carries; the deleted names and what replaced each of them are written down only in
210
+ the Harness repository's own `docs/HOSTED-PARITY.md`, which is not installed beside this document.
177
211
 
178
212
  Most of the survivors take a run identifier where the deleted local twin took a folder. That is
179
213
  the whole difference in the vocabulary: a hosted build has no folder, and the run identifier is
@@ -216,7 +250,7 @@ machine that is only using the product installs from `pip` instead.
216
250
  ## Cloud authentication preflight
217
251
 
218
252
  Authentication begins only when the next command will contact the Mostly Right cloud. Immediately
219
- before hosted `mr-data probe` or `mr-data recipe`, run
253
+ before hosted `mr-data dataset create` or `mr-data recipe`, run
220
254
  `mr-data whoami --json` and read its `status`, and it intentionally does not report
221
255
  `MOSTLYRIGHT_API_KEY`: when the variable is set, `whoami` validates that effective environment
222
256
  credential and reports stored-device metadata separately. It never prints the environment value and
@@ -394,23 +428,30 @@ provider or bypass it.
394
428
  reason codes only after the discovery lanes and source comparisons are complete. Claim
395
429
  `unsupported` only after every typed-refusal chain ends `lawful_routes_exhausted`.
396
430
 
397
- Reading a registered source's bytes before you commit to it is one command:
431
+ **Reading the bytes is the sample run, not `mr-data probe`.** `probe SOURCE_ID QUESTION_ID
432
+ --dataset DATASET_ID` takes two identifiers rather than a question in words, and the question
433
+ identifier is one no v4 registration receipt returns; the command also rides the frozen
434
+ `/v3/sessions` routes, which a deployment may have switched off. So the reading path on a hosted
435
+ workspace is the ordinary loop, one stage early: register the recipe, start an unwindowed sample
436
+ under a row ceiling, and read what came back.
398
437
 
399
438
  ```sh
400
- mr-data probe SOURCE_ID "does this feed carry city grain before 2021" --json
439
+ mr-data run --recipe RECIPE_ID --digest RECIPE_DIGEST --mode sample --max-rows 500 --json
440
+ mr-data peek RUN --json # the columns and types the run sealed
441
+ mr-data query RUN "select count(*) from run_table" --json
442
+ mr-data receipt RUN --json # what was fetched and what those bytes hashed to
401
443
  ```
402
444
 
403
- The session is plumbing and is implicit: it is opened or reused for that source, asked, and closed
404
- when the command returns, on the failure path as well as the success path, so a refused probe
405
- leaves no warm worker running. Keep its status, reason code, and exit value internal; when a probe
406
- result affects a source decision, describe only what it settled about the data.
445
+ That is stages 4 through 6 run once to settle the shape, and a sample that already meets the
446
+ requirements is the table a full run would seal. Where the deployment does answer a probe, keep its
447
+ status, reason code and exit value internal; when a probe result affects a source decision, describe
448
+ only what it settled about the data.
407
449
 
408
- **A probe answer is evidence, and it is cheap to reuse.** The v3 doctrine that a probe was never
409
- evidence is repealed. Every acquisition — a probe's or a run's — produces a receipt, and
410
- acquisitions are cacheable by source, query digest and window, so a probe's fetch feeds a sample
411
- built in the same warm session without paying for the bytes twice. In v4.0 that cache is
412
- session-scoped by owner ruling: it lives for the session's lifetime and no longer, so do not plan
413
- around a probe from an hour ago still being warm.
450
+ **An acquisition's answer is evidence, and it is cheap to reuse.** The v3 doctrine that a probe was
451
+ never evidence is repealed. Every acquisition produces a receipt, and acquisitions are cacheable by
452
+ source, query digest and window, so a fetch feeds a later build in the same warm session without
453
+ paying for the bytes twice. In v4.0 that cache is session-scoped by owner ruling: it lives for the
454
+ session's lifetime and no longer, so do not plan around a fetch from an hour ago still being warm.
414
455
 
415
456
  Where a source needs a credential, enrol it by name first — see
416
457
  [Source credentials](#source-credentials) — and reference the name from the recipe document. Where
@@ -480,7 +521,7 @@ mr-data recipe RECIPE.json --json
480
521
  One call. The server canonicalizes the bytes, computes the digest, and registers the dataset, the
481
522
  question, the sources, the connector configs and the table plan in one transaction, answering with
482
523
  `recipe_id`, `recipe_digest`, `dataset_id`, `table_id` and `source_ids`. Read
483
- [The recipe document](../../docs/RECIPE-DOCUMENT.md) for the shape of every member.
524
+ [The recipe document](https://mostlyright.md/docs/reference/recipe/) for the shape of every member.
484
525
 
485
526
  **Name the dataset the recipe binds to.** The document's `dataset` member takes an optional `id`,
486
527
  and it is the identifier stage 1 printed. With it, the registration attaches to the page somebody
@@ -768,25 +809,36 @@ contradicts what the column declared. A `bucket` the span outgrew is not one of
768
809
  is widened and the chart stays yours. So `none` is the right answer to write for a column you
769
810
  already know has no shape, and a wide date range is worth a thought before you promise a timeline.
770
811
 
771
- [The recipe document](../../docs/RECIPE-DOCUMENT.md) carries which types each chart admits, what
772
- the defaults are, and the two members only some charts take.
812
+ [The recipe document](https://mostlyright.md/docs/reference/recipe/) carries which types each
813
+ chart admits, what the defaults are, and the two members only some charts take.
773
814
 
774
815
  ### What the statement must actually return
775
816
 
776
- Every acquired source is bound as all-text CSV — `read_csv(header = true, auto_detect = false,
777
- delim = ',')` with every column `VARCHAR` — so each declared type is an explicit cast in the
778
- statement, and the cast is checked against what the engine returns:
817
+ The last step's relation is projected into the declared columns: every declared column must come
818
+ back BY NAME, in an engine type that narrows into the declared one. A returned column the table does
819
+ not declare is dropped; a declared column no statement returns is `TRANSFORM_PLAN_INVALID` before a
820
+ row exists.
821
+
822
+ A source's `binding` decides what its columns are typed as. `text` — the default, and what leaving
823
+ the member out means — binds every column `VARCHAR` whatever the file said, so each declared type is
824
+ an explicit cast you write. `typed` keeps the types the bytes carry. Under either one the cast is
825
+ checked against what the engine returns:
779
826
 
780
827
  | Declared | The engine type the statement must return |
781
828
  | --- | --- |
782
829
  | `string` | `VARCHAR` |
783
830
  | `integer` | `TINYINT`, `SMALLINT`, `INTEGER`, `BIGINT` — not the widest |
784
- | `decimal` | `DECIMAL(p,s)` — never `FLOAT` or `DOUBLE` |
831
+ | `decimal` | `DECIMAL(p,s)` with `p` at most 38 — never `FLOAT` or `DOUBLE` |
832
+ | `float` | `DOUBLE`, `FLOAT` |
785
833
  | `boolean` | `BOOLEAN` |
786
834
  | `date` | `DATE` |
787
835
  | `timestamp` | `TIMESTAMP WITH TIME ZONE` |
788
836
  | `json` | `JSON` or `VARCHAR` |
789
837
 
838
+ `decimal` refusing the floating types is the point of that split: an average or a division over
839
+ exact decimals comes back `DOUBLE`, so end the expression in `CAST(… AS DECIMAL(p,s))` where the
840
+ column is exact, and declare `float` where it is genuinely approximate.
841
+
790
842
  `TRANSFORM_COLUMN_TYPE_MISMATCH: column obs_time is declared timestamp and the statement returns
791
843
  TIMESTAMP` is the time-zone row, and it names the same word twice because the difference is the zone
792
844
  rather than the type. Build one from an epoch with `to_timestamp(CAST(x AS BIGINT))`, or from text by
@@ -823,12 +875,22 @@ ones, so a caller cannot assert a digest for its own bytes even by trying; the d
823
875
  is the server's reading of what it canonicalized. Registration is idempotent by that digest:
824
876
  identical bytes register one recipe rather than two.
825
877
 
826
- Before proposing a step, read [Transforms](#transforms); before proposing a future outcome, read
827
- [Prediction labels](#prediction-labels). Declare what each measured column measures as a unit code
828
- from the accepted subset — `cm`, `ug/m3`, `km/h` and the rest of it are written out in
829
- [Units](../../docs/UNITS.md). Do not invent a unit name: a code outside the subset is refused with
830
- the construct that put it there. Say `none` where a column carries no physical unit. Remember that
831
- the two temperature scales are offsets rather than multipliers.
878
+ Before writing a step, read [Transforms](#transforms); before proposing a future outcome, read
879
+ [Prediction labels](#prediction-labels). Declare what each measured column measures as a UCUM code
880
+ from the accepted subset — `Cel`, `ug/m3`, `km/h`, `hPa`, `{count}` and the rest of it are written
881
+ out in [Units](https://mostlyright.md/docs/reference/units/). Write the code rather than a name:
882
+ `ug/m3` composes and `microgram_per_cubic_meter` does not.
883
+
884
+ **Nothing resolves the code, so there is no `UNIT_*` refusal to lean on.** The recipe schema bounds
885
+ the length of `unit` and nothing else; registration checks only that `units[].column` names a
886
+ declared column and answers `422 RECIPE_DOCUMENT_INCOHERENT` at `/units/N/column` when it does not;
887
+ the run never reads `units` at all. A code outside the subset therefore costs nothing at build time
888
+ and costs every reader afterwards, and a column declared in metres that holds feet is caught by
889
+ nothing. Convert in the statement, then declare the unit of what the column ends up holding. Leave a
890
+ column out of `units` rather than declare something approximate — a density, a rate, anything whose
891
+ unit is genuinely new. `none` is not a unit and does not become one. Remember that the two
892
+ temperature scales are offsets rather than multipliers, so `Cel` composes with nothing: `Cel/m` and
893
+ `mCel` are both outside the subset.
832
894
 
833
895
  ### 5. Build — take an unwindowed sample first
834
896
 
@@ -886,7 +948,7 @@ one", not as a failure: a document declaring 8 MiB over a feed that answers 40 K
886
948
  ceiling on that source:** that does not fetch less data, it truncates the sample or refuses the
887
949
  full run. Split the window across several sources — one per month, per region, or per whatever the
888
950
  publisher paginates on — and union them in a first transform step. A recipe names up to 256
889
- sources, which is a great many windows. [The recipe document](../../docs/RECIPE-DOCUMENT.md)
951
+ sources, which is a great many windows. [The recipe document](https://mostlyright.md/docs/reference/recipe/)
890
952
  carries the measured numbers behind the ceiling.
891
953
 
892
954
  **A corpus of many pages behind one index is a COLLECTION, not many sources.** See
@@ -1352,9 +1414,9 @@ access is denied, say the current account cannot view the build evidence and ask
1352
1414
  to an authorized account or request access.
1353
1415
 
1354
1416
  A flag that cannot mean what it means locally is accepted and reported, never refused.
1355
- `mr-data runs --depth 3` runs, lists the workspace's runs, and reports under `flags_without_effect`
1356
- that folder depth described nothing here. Read that key before repeating an argument that did
1357
- nothing. Only flags actually passed are reported.
1417
+ `mr-data status RUN_ID --receipts` runs, reports the run, and reports under `flags_without_effect`
1418
+ that a run's digests, sources, timings and checks are already on the record. Read that key before
1419
+ repeating an argument that did nothing. Only flags actually passed are reported.
1358
1420
 
1359
1421
  ### Asking the table a question
1360
1422
 
@@ -1382,7 +1444,7 @@ Two things are currently broken here and are not worth diagnosing again:
1382
1444
  version digest in `source.connection`, with the query in `parameter_values` and a valid response
1383
1445
  reader; set `dataset.id` to the granted dataset. Do not ask the user to re-enter an already saved
1384
1446
  credential under Keys. If it is not granted, explain that an owner or administrator must allow
1385
- the dataset in Settings → Connections & secrets. See [Saved connections](../../docs/RECIPE-CONNECTIONS.md)
1447
+ the dataset in Settings → Connections & secrets. See [Saved connections](https://mostlyright.md/docs/build/credentials/)
1386
1448
  for the proposal shape. Studio resolves it into the canonical recipe and enforces access during
1387
1449
  execution. Named secrets below remain available for sources without saved connections.
1388
1450
 
@@ -1646,8 +1708,9 @@ or network search. If every registered, lawful alternative is deterministically
1646
1708
  record and report the typed impossibility. Do not loop forever, fabricate a dataset, or turn a
1647
1709
  refusal into permission.
1648
1710
 
1649
- Never download source bytes directly. Never write custom Python, SQL, or shell transformations.
1650
- Never create Parquet outside a Harness Build. Never report a standalone Parquet file as a Build.
1711
+ Never download source bytes directly. Never transform data on this computer: the transform is the
1712
+ recipe's own `duckdb_sql` steps, run on the backend, and Python, shell or a local SQL engine reading
1713
+ the same bytes is not that. Never create Parquet outside a Harness Build. Never report a standalone Parquet file as a Build.
1651
1714
  Do not report completion unless the run succeeded, its declared checks were read, and any artifacts
1652
1715
  the user asked for came back with their digests checked.
1653
1716
 
@@ -1774,20 +1837,24 @@ the records pointer names an array, and whether `discovery.max_requests` plus
1774
1837
  they are the four that are cheapest to get right before a run spends anybody's rate limit.
1775
1838
 
1776
1839
  The full contract, including every bound and the coverage block's members, is
1777
- [Source collections](../../docs/SOURCE-COLLECTIONS.md).
1840
+ [Many-page sources](https://mostlyright.md/docs/build/collections/), with the connector coordinate
1841
+ and its parameters under
1842
+ [Source kinds and connectors](https://mostlyright.md/docs/reference/sources/).
1778
1843
 
1779
1844
  ## Readers
1780
1845
 
1781
- `docs/READERS.md` defines Reader behavior. Certified Readers may be pinned on an exact
1782
- `ArtifactSourceInput` for `user.file` or on a credential-free public HTTPS source. A Recipe pins the
1783
- exact Reader name and version. Unsupported versions refuse by name. A version change is a recipe
1784
- revision.
1846
+ [Readers](https://mostlyright.md/docs/reference/readers/) defines Reader behavior. Certified
1847
+ Readers may be pinned on a hash-pinned
1848
+ `user.file` artifact, on a credential-free public HTTPS source, or on a bounded ordered public HTTPS
1849
+ collection, which applies one exact pin to every member. A Recipe pins the exact Reader name and
1850
+ version. Unsupported versions refuse by name. A version change is a recipe revision.
1785
1851
 
1786
1852
  **Direct CSV supports publisher CSV aliases, including Eurostat SDMX-CSV.**
1787
1853
  Accepted labels include `text/csv`, `text/plain`, `application/csv`, `application/x-csv`,
1788
1854
  `text/x-csv`, and `application/vnd.sdmx.data+csv`. For title rows, alternate encodings,
1789
1855
  headerless files, or padded columns, pin `delimited_text@2.0.0` and configure the layout; see
1790
- `docs/READERS.md`. A publisher needing another format can use its matching Reader.
1856
+ [Readers](https://mostlyright.md/docs/reference/readers/). A publisher needing another format can
1857
+ use its matching Reader.
1791
1858
 
1792
1859
  **A v4 recipe pins a Reader through `connector.parameters`, under three reserved names.**
1793
1860
  `declared_source` is closed and has no member for a decoder, so the pin rides on the parameter array
@@ -1807,38 +1874,35 @@ field and finding none does not mean v4 cannot pin one.
1807
1874
  }
1808
1875
  ```
1809
1876
 
1810
- A parameter under `reader.` that is not one of those three is refused rather than ignored.
1877
+ A parameter under `reader.` that is not one of those three is refused rather than ignored. All three
1878
+ or none: there is no other place a Reader is named. Read the pin grammar and the closed registry of
1879
+ 48 coordinates at [Readers](https://mostlyright.md/docs/reference/readers/).
1880
+
1881
+ **No command tries a Reader against a URL.** `mr-data peek` reads the preview one hosted run sealed,
1882
+ and its `--reader`, `--reader-options`, `--format` and `--json-*` flags are accepted and print `no
1883
+ effect here; the hosted preview arrives already decoded`. A Reader is settled the way every other
1884
+ part of the document is settled: pin the coordinate in the recipe, register it, take an unwindowed
1885
+ sample under `--max-rows`, and read the columns back with `peek` and the decode evidence with
1886
+ `receipt`. A wrong coordinate or a wrong settings object is a typed refusal on that run naming the
1887
+ source, which is the answer — change the pin and register the revision.
1811
1888
 
1812
1889
  **`reader.decode_options` is the canonical JSON text of the options the family itself admits**, with
1813
1890
  every family-defaulted key filled in. A stated subset digests differently from the pin the worker
1814
- reconstructs and is refused. Derive the value rather than writing it:
1815
-
1816
- ```python
1817
- from mostlyright.data_harness.readers.json_tabular import JsonTabularReaderV1_1
1818
- from mostlyright.data_harness.canonical import canonical_json_bytes
1819
- admitted = JsonTabularReaderV1_1().validate_options(
1820
- {"columns": [{"name": "icao_id", "pointer": "/icaoId"}]}
1821
- )
1822
- value = canonical_json_bytes(admitted).decode()
1823
- ```
1891
+ reconstructs and is refused. This client ships no Reader code to derive the value with: copy the
1892
+ family's complete default object from [Readers](https://mostlyright.md/docs/reference/readers/),
1893
+ change only the keys the source needs, keep every other key at its default, and write it as
1894
+ canonical JSON — keys sorted, no whitespace.
1824
1895
 
1825
1896
  A column that may be absent from a record must state `"required": false`. Publishers routinely omit
1826
1897
  a key rather than sending null, and a required column that is missing refuses the record.
1827
1898
 
1828
- **Open defect: an empty string in the admitted options is refused at the acquisition boundary with
1829
- `TEXT`, and the refusal names no source.** `records_pointer: ""` is the RFC 6901 pointer for a
1830
- root-level array, which is what `json.tabular` admits by default and what every API returning a bare
1831
- JSON array needs, so this blocks that whole class of source. The request-side screen applies the
1832
- identifier rule — where an empty string is a missing one — to JSON values, where it is not. Fixed by
1833
- harness PR #503; until that reaches the deployed worker image, a root-array JSON source cannot be
1834
- pinned. Confirm the deployed worker carries the fix before planning a build around one.
1835
-
1836
1899
  When you pin a Reader for a source nobody has approved yet, name the family's highest certified
1837
1900
  version rather than its base version. The base version refuses a publisher that merely declined
1838
1901
  to describe its file, and a weak-label widening lands as a new coordinate precisely so an
1839
1902
  approved recipe is not moved under review's feet -- which makes the base version the wrong
1840
- default for a source that has no approval to protect. `ReaderRegistry.widest_version` answers
1841
- which coordinate that is. For a family whose major version has moved, the highest coordinate is
1903
+ default for a source that has no approval to protect. The registry table at
1904
+ [Readers](https://mostlyright.md/docs/reference/readers/) lists every certified coordinate of a
1905
+ family; take the highest within the major you want. For a family whose major version has moved, the highest coordinate is
1842
1906
  a different output contract rather than a wider door onto the same one, so choose it
1843
1907
  deliberately: `weather.grib2@2.0.0` emits columns `1.0.0` does not.
1844
1908
 
@@ -1918,78 +1982,117 @@ response matches the family's exact media-type, settings, and budget contract.
1918
1982
 
1919
1983
  ## Transforms
1920
1984
 
1921
- The graph operations are a closed set, and what gets into that set is decided by one written test:
1922
- a step is cleaning when the sources plus the declared shape determine it, and it is a modeling
1923
- choice when it needs an assumption about what the analyst intends to model. Read
1924
- [Transforms a plan may perform](../../docs/TRANSFORMS.md) before proposing a step that is not
1925
- already in the vocabulary; do not work around a refused operation with a chain that reproduces it.
1926
-
1927
- Four points recur in plans:
1928
-
1929
- - Division is an expression inside `derive`. Numerator first, denominator second, both operands in
1930
- one exact numeric type, and the result is always the exact decimal type — dividing two whole
1931
- numbers never rounds down or towards zero. Declare `zero_denominator_policy` and
1932
- `null_denominator_policy` on any node that divides, and on no other node. A zero denominator
1933
- stops the run whether or not the numerator is absent. For a rate per hundred thousand, multiply
1934
- inside the division rather than after it. Write a conversion with the division last —
1935
- `(x * 9 + 160) / 5` rather than `x * 9 / 5 + 32` — because every operation takes operands of one
1936
- exact numeric type and a quotient lands in the decimal type.
1937
- - `unpivot` turns one column per period into rows. Name the value-bearing columns in the plan,
1938
- name the two output columns, declare `value_type` for the long column, and set `null_policy` to
1939
- `retain`; drop absent cells with a separate `filter` step if the contract calls for it. Every
1940
- named column must already be the declared type.
1941
- - Join kinds are `left`, `inner`, and `anti`, and all three are graph kinds. Use `anti` for "which
1942
- of these has none of those"; do not join and then filter on an absent column, which cannot tell a
1943
- missing counterpart from a legitimately absent value. An anti join emits left columns only, takes
1944
- `unmatched_policy` `preserve`, and has its declared cardinality checked on the left side. The
1945
- older plan version's single join is `left` and stays `left` — a settled ruling, not a gap — so a
1946
- question needing `inner` or `anti` is written as a graph.
1947
- - `date_add_days` is the one fixed calendar availability correction. It takes a declared `date`
1948
- column, a new output column, and an integer `days` from -3650 to 3650; it keeps nulls null and
1949
- rejects a result outside the supported date range. Use it for a source-established statement
1950
- such as `information_available_date = rate_date + 1`; it never reads another row and is not a
1951
- lag, lead, target, or replacement for `prediction_label`. Date-only arithmetic remains a
1952
- calendar-day operation at DST boundaries.
1953
- - `trim` and `case_fold` are the whole text-normalization surface, and there is no pattern
1954
- matching. `trim` removes six named characters; `case_fold` lowers twenty-six letters and touches
1955
- nothing else. Text needing more than that is normalized before it reaches the plan.
1956
-
1957
- Declare what each measured source column is in, as `column_units` on the source node, and what
1958
- unit a computed column is in, as `output_unit` on its expression. Both are optional and both are
1959
- what the two unit checks read: a merge whose declared units disagree is refused, and a
1960
- conversion that is not the arithmetic those two units imply is refused with the arithmetic they
1961
- do imply. A column you leave undeclared is left alone by both, and a column with no physical
1962
- unit is left out rather than declared `none`. Declare `output_unit` only where the expression
1963
- converts one declared column, or where its operands settle the answer, or where nothing it
1964
- reads is declared; a computed column whose unit is genuinely new -- a density, an area, a rate
1965
- -- declares nothing, because nothing can check it.
1985
+ **A v4 transform is SQL, not an operation vocabulary.** The member is
1986
+ `{"engine": "duckdb_sql", "steps": [...]}`, and each step is `{"step_id": "…", "sql": "…"}` with an
1987
+ optional `description`: at most 64 steps, each statement at most 65536 characters. There is no node
1988
+ graph, no closed operation set and nothing to work around — the statement is the plan. The schema
1989
+ also admits `engine: "none"`, and that document registers and then refuses its first run
1990
+ `TRANSFORM_PLAN_INVALID`, as an empty `steps` array does, because neither says what a build
1991
+ produces. Fetch [Transforms](https://mostlyright.md/docs/reference/transforms/) before writing one:
1992
+ it carries the admitted function table, the worked shapes and every refusal code.
1993
+
1994
+ **What a step reads.** One relation per source, under the source's own `name`, and one relation per
1995
+ earlier step, under its `step_id`. Steps run in array order, never sorted, so a later step reads an
1996
+ earlier one by writing its id. A `step_id` equal to a source `name` is refused
1997
+ `TRANSFORM_STEP_SHADOWS_SOURCE` and two steps sharing an id are `TRANSFORM_STEP_DUPLICATE`. Quote a
1998
+ name that is also a SQL keyword.
1999
+
2000
+ **What the columns are typed as.** The relation carries the ordered header acquisition recorded, and
2001
+ the source's `binding` decides the types: `text` — the default, and what leaving the member out
2002
+ means — binds every column `VARCHAR`, and `typed` keeps what the bytes carry. Under `text` every
2003
+ declared type is a cast you write, which keeps casting an act of the recipe. Two relations are not
2004
+ declared anywhere: a recorded stream source presents one of two fixed text headers — eight
2005
+ columns `epoch`, `event_id`, `event_time`, `message`, `message_class`, `message_type`,
2006
+ `received_at`, `sequence`, or those plus `member` after `event_time` when the connector declares a
2007
+ member field — and a collection source presents the pinned Reader's columns plus seven reserved
2008
+ `page_*` columns.
2009
+
2010
+ **What the last step must return** is under
2011
+ [What the statement must actually return](#what-the-statement-must-actually-return). Row order is
2012
+ the table's `grain` and then every remaining declared column in declaration order, so an `order by`
2013
+ you write is not the sealed order.
2014
+
2015
+ ```json
2016
+ "transform": {
2017
+ "engine": "duckdb_sql",
2018
+ "steps": [
2019
+ {
2020
+ "step_id": "readings",
2021
+ "sql": "select station, cast(concat(valid, ':00+00') as timestamp with time zone) as observed_at, cast((try_cast(tmpf as decimal(6,2)) - 32) * 5 / 9 as decimal(6,2)) as air_temp_c from asos_observations where valid is not null and valid <> ''"
2022
+ },
2023
+ {
2024
+ "step_id": "hourly",
2025
+ "sql": "select station, time_bucket(interval 1 hour, observed_at) as hour_start, cast(avg(air_temp_c) as decimal(6,2)) as mean_air_temp_c, count(*) as reading_count from readings group by station, time_bucket(interval 1 hour, observed_at)"
2026
+ }
2027
+ ]
2028
+ }
2029
+ ```
2030
+
2031
+ `asos_observations` is a source's `name`; `readings` is the first step read by the second. The outer
2032
+ `cast` on each measured column is what makes it a `DECIMAL(6,2)` — without it the division and the
2033
+ `avg` both return `DOUBLE` and the column is refused.
2034
+
2035
+ **One read-only `SELECT` per step.** The engine classifies the statement: more than one is
2036
+ `TRANSFORM_STATEMENT_NOT_SINGLE`, and anything that is not a `SELECT` is
2037
+ `TRANSFORM_STATEMENT_NOT_SELECT` — so `ATTACH`, `COPY`, `INSTALL`, `LOAD`, `PRAGMA`, `CREATE`,
2038
+ `INSERT`, `UPDATE` and `DELETE` are all refused. Common table expressions, `UNION`, `QUALIFY`,
2039
+ `UNPIVOT`, `VALUES` and `SELECT DISTINCT` are each one `SELECT` and are admitted, which is how a
2040
+ long-format reshape, a join, a deduplication and a time bucket are written.
2041
+
2042
+ **The result has to re-derive, and the engine decides that rather than a word list.** A sealed table
2043
+ is rebuilt and compared byte for byte, so a step may read nothing the recipe and the acquired bytes
2044
+ do not fix. The engine's own function catalog is asked about every name the statement references:
2045
+ anything it does not report as re-derivable, anything with side effects, and anything it does not
2046
+ carry at all is refused `TRANSFORM_PLAN_INVALID` before the step runs. That catches `current_date`,
2047
+ `current_timestamp`, `now()`, `today()`, `random()`, `gen_random_uuid()` and anything written in
2048
+ terms of them. The date-granular ones are the dangerous ones: both executions inside one build agree
2049
+ and the table seals, and tomorrow's refresh can never reproduce it. Reach a column a publisher
2050
+ happened to spell like one of those names by qualifying it — `observations.current_date`.
2051
+
2052
+ **The engine reaches nothing.** It is given this run's own source files and scratch directory, then
2053
+ external access is switched off and the settings are locked, so it can open no file, fetch no URL
2054
+ and load no extension. There is no denylist of names in any of it, by design: a source is reached by
2055
+ naming its relation and never by opening its address again. Where that confinement cannot be
2056
+ established the code is `TRANSFORM_ENGINE_UNCONFINED` and no step was attempted.
2057
+
2058
+ **Every table is built in `UTC`**, established before a statement is read. A recipe declaring
2059
+ another `timezone` registers and then refuses its first run `RUN_RECIPE_INVALID`. `ICU` is not
2060
+ loaded, so `AT TIME ZONE` is unavailable; write the conversion into the statement.
2061
+
2062
+ A step the engine itself refused comes back as `RUN_EXECUTION_FAILED` carrying DuckDB's own code and
2063
+ words — `step daily_mean would not run: BINDER_ERROR: …`. Read the detail, not the code. Every
2064
+ transform code reports `failed_stage: "transform"`.
2065
+
2066
+ `units` is declared beside the table, and nothing in the transform reads it or checks the arithmetic
2067
+ against it. Convert in the statement, then declare the unit of what the column ends up holding, per
2068
+ the paragraph under [What each check kind requires](#what-each-check-kind-requires).
1966
2069
 
1967
2070
  ## Prediction labels
1968
2071
 
1969
- `prediction_label@1.0.0` is retired. Do not put it in a plan, and do not offer a future outcome as
1970
- something the harness can construct. Authoring, plan checking, validation, export and approval all
1971
- refuse a graph naming it, with a typed `OPERATION_RETIRED` refusal.
2072
+ There is no prediction-label member in a v4 recipe document and no operation that produces one. Do
2073
+ not offer a future outcome as something the harness can construct.
1972
2074
 
1973
- The reason is the admission contract above: the horizon such a label needs is a statement about what
1974
- somebody intends to model, and nothing in the sources says what it should be. A target belongs to
1975
- the modeling step that consumes the dataset, not to the dataset. There is no replacement operation,
1976
- and none is owed.
2075
+ The horizon such a label needs is a statement about what somebody intends to model, and nothing in
2076
+ the sources says what it should be. A target belongs to the step that consumes the dataset, not to
2077
+ the dataset. There is no replacement, and none is owed.
1977
2078
 
1978
- Do not reconstruct it either. `lag`, `lead`, `rolling` and `window_target` are refused by name; a
1979
- derive expression, a self join, or custom code that reproduces the same shift is the same thing
1980
- this contract refuses, whether or not any single check catches it. If a contract asks for a future
1981
- outcome, say that the harness builds the observed table and that the horizon is chosen downstream.
1982
- Set requirements `target_policy` to `none`.
2079
+ Do not reconstruct it in the statement either. A window function that shifts a measured value
2080
+ forward in time and a self join that reads a later row are the same claim under another spelling,
2081
+ and no check catches either — the transform engine refuses what cannot re-derive, not what is
2082
+ modelled. Where a request asks for a future outcome, say that the build seals the observed table and
2083
+ that the horizon is chosen downstream.
1983
2084
 
1984
- Work sealed or approved before the retirement is unaffected: such a Build still verifies and
1985
- replays, and such a Recipe still runs. [Prediction labels](../../docs/PREDICTION-LABELS.md) is the
1986
- contract for reading it.
2085
+ Work sealed before the retirement is unaffected: such a table still verifies and replays, and such a
2086
+ Recipe still runs.
2087
+ [Units and vocabulary](https://mostlyright.md/docs/reference/units/) records the withdrawal and what
2088
+ a reader of such a Build should expect from it.
1987
2089
 
1988
2090
  ## Boundaries
1989
2091
 
1990
- - Keep plans and Recipes declarative. Do not embed Python, SQL, shell, arbitrary expressions,
1991
- plugins, redirects, or callables.
1992
- - Use only contract-supported operations, joins, adapters, checks, and resource bounds.
2092
+ - The transform's `duckdb_sql` steps are the only executable text a Recipe carries, and they run on
2093
+ the backend under the confinement above. Everything else in the document stays declarative: no
2094
+ Python, no shell, no plugins, no redirects, no callables.
2095
+ - Use only contract-supported connectors, Readers, checks and resource bounds.
1993
2096
  - Do not claim that recipe registration fetched a source, that a declared check released a table
1994
2097
  version, or that agent output executed a transformation.
1995
2098
  - Preserve the cause and acceptance condition for blocker, critical, and high findings. This
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Mostly Right Data Build"
3
3
  short_description: "Build and verify reviewed datasets"
4
- default_prompt: "Use $mr-data-build to autonomously deliver the requested dataset outcome before inspecting CLI help, documentation, schemas, fixtures, examples, prior runs, or installed package details whenever the request uses, tests, demonstrates, or debugs the Harness. If the host requires a skill-use disclosure, send only one outcome-specific sentence naming $mr-data-build before the first tool call; otherwise start silently. Never narrate preparation, skill loading, CLI discovery, authentication, document or contract lookup, example searches, package versions, command batches, or acquisition-document authoring. Never narrate skill instructions, commands, receipts, events, protocols, status codes, service behavior, or notebook mechanics. Open the request's canonical Dataset page first when that address can be minted, and carry on without it rather than blocking. Keep routine work silent unless the host requires periodic status; then send concise outcome-oriented updates without mechanics or internal language, grounded in domain-specific observed evidence, a decision, required user action, or the verified outcome."
4
+ default_prompt: "Use $mr-data-build to autonomously deliver the requested dataset outcome before inspecting CLI help, documentation, schemas, fixtures, examples, prior runs, or installed package details whenever the request uses, tests, demonstrates, or debugs the Harness. If the host requires a skill-use disclosure, send only one outcome-specific sentence naming $mr-data-build before the first tool call; otherwise start silently. Never narrate preparation, skill loading, CLI discovery, authentication, document or contract lookup, example searches, package versions, command batches, or acquisition-document authoring. Never narrate skill instructions, commands, receipts, events, protocols, status codes, service behavior, or notebook mechanics. Open the request's canonical Dataset page first when that address can be minted, and carry on without it rather than blocking. Keep routine work silent unless the host requires periodic status; then send concise outcome-oriented updates without mechanics or internal language, grounded in domain-specific observed evidence, a decision, required user action, or the verified outcome. Fetch the published reference at https://mostlyright.md/docs/ for the recipe field, connector, Reader, transform, check, unit, command flag, error code, ceiling or worked example in front of you before drafting it; it is the contract, it wins over any memory of an older version, and fetching it is preparation you never narrate."