mostlyright-data 0.26.1__tar.gz → 0.28.0__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 (120) hide show
  1. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/PKG-INFO +1 -1
  2. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/pyproject.toml +1 -1
  3. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/SKILL.md +24 -9
  4. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +7 -0
  5. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +9 -5
  6. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +20 -4
  7. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +19 -0
  8. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +8 -0
  9. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/commands.md +8 -2
  10. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/live-run.md +4 -2
  11. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/making-a-table-incremental.md +95 -7
  12. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/readers.md +2 -2
  13. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/receipts.md +6 -3
  14. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/required-protocol.md +3 -1
  15. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/sources.md +14 -7
  16. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/activity.py +272 -130
  17. mostlyright_data-0.28.0/src/mostlyright/data_harness/thin/fast_features.py +354 -0
  18. mostlyright_data-0.28.0/src/mostlyright/data_harness/thin/progress_rate.py +347 -0
  19. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe.py +263 -29
  20. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe_explain.py +61 -33
  21. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe_forecast.py +200 -14
  22. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe_lint.py +252 -0
  23. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/router.py +7 -3
  24. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/session.py +16 -1
  25. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/studio_capabilities.py +5 -1
  26. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4.py +31 -9
  27. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_datasets.py +305 -9
  28. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_query.py +164 -28
  29. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_runs.py +35 -2
  30. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_stream.py +14 -1
  31. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/render.py +15 -0
  32. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/.gitignore +0 -0
  33. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/README.md +0 -0
  34. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/scripts/hatch_build.py +0 -0
  35. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/agents/openai.yaml +0 -0
  36. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
  37. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
  38. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
  39. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
  40. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/agent-protocol.md +0 -0
  41. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
  42. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
  43. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/boundaries.md +0 -0
  44. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
  45. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
  46. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/installation-parity.md +0 -0
  47. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/narrating-the-run.md +0 -0
  48. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
  49. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/one-install.md +0 -0
  50. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/prediction-labels.md +0 -0
  51. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/promote.md +0 -0
  52. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
  53. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
  54. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/reference-pages.md +0 -0
  55. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/source-credentials.md +0 -0
  56. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
  57. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/transforms.md +0 -0
  58. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/user-communication-contract.md +0 -0
  59. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
  60. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  61. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/__init__.py +0 -0
  62. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/agent_protocol.py +0 -0
  63. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/canonical.py +0 -0
  64. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/formats.py +0 -0
  65. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  66. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/key_seam.py +0 -0
  67. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/page_coverage.py +0 -0
  68. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  69. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/session_probes.py +0 -0
  70. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/skill_assets.py +0 -0
  71. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/table_manifest.py +0 -0
  72. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  73. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  74. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  75. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  76. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/categories.py +0 -0
  77. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/commands.py +0 -0
  78. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  79. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/download.py +0 -0
  80. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  81. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/parity.py +0 -0
  82. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/probe.py +0 -0
  83. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  84. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/propose.py +0 -0
  85. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  86. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/research.py +0 -0
  87. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
  88. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/runs.py +0 -0
  89. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/stream.py +0 -0
  90. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  91. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/transport.py +0 -0
  92. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  93. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
  94. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  95. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
  96. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  97. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  98. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  99. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  100. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
  101. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
  102. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  103. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
  104. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
  105. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  106. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  107. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  108. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  109. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  110. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  111. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  112. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  113. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  114. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  115. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  116. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  117. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/login.py +0 -0
  118. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  119. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  120. {mostlyright_data-0.26.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/remediation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mostlyright-data
3
- Version: 0.26.1
3
+ Version: 0.28.0
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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mostlyright-data"
3
- version = "0.26.1"
3
+ version = "0.28.0"
4
4
  description = "Mostly Right hosted CLI for reviewed datasets"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -14,10 +14,16 @@ references. Load the reference for the current stage only, not the entire librar
14
14
  1. Run `mr-data whoami --json` once. If authentication needs repair, read
15
15
  [authentication](references/cloud-authentication-preflight.md) and follow the reported action.
16
16
  Never print credentials or bypass their storage protection.
17
- 2. Create the dataset once with `mr-data dataset create --name "Working title" --handoff --json`.
18
- Keep its `dataset_id`. Link the stable `dashboard_url` in the first build message; open the
19
- separate `navigation.url` in the host's visible browser if available. A missing handoff does
20
- not undo creation: do not create a duplicate dataset.
17
+ 2. If the user handed you a `dataset_id` with the request, that dataset is the one: its page is
18
+ already open in their browser. Do not create another, do not run `mr-data open`, and put that
19
+ id in the recipe's `dataset.id`. Otherwise create the dataset once with
20
+ `mr-data dataset create --name "Working title" --handoff --json` and keep its `dataset_id`.
21
+ A create that answers `open_request_reused` made nothing: it found the dataset the user already
22
+ opened for this request, so build into that `dataset_id` and do not open it again; use
23
+ `--separate` only when the user asked for another dataset. After a real create, link the stable
24
+ `dashboard_url` in the first build message and open the separate `navigation.url` in the
25
+ host's visible browser if available. A missing handoff does not undo creation: do not create a
26
+ duplicate dataset.
21
27
  3. Settle purpose, row grain, tables, coverage, required sources and update cadence before
22
28
  dependent work. In an attended build, ask unresolved material questions in one concise message
23
29
  with recommended choices; recording an assumption is not asking the user. Default to one joined,
@@ -46,16 +52,25 @@ references. Load the reference for the current stage only, not the entire librar
46
52
  Decide how each source continues before you register it, and whether the table needs a
47
53
  declared `rebuild`: read [making a table incremental](references/making-a-table-incremental.md).
48
54
  Run `mr-data recipe explain RECIPE.json --json` first. It names every reason the table could
49
- not refresh, not only the first. Before any `--full` of a paid collection, run
50
- `mr-data recipe forecast RECIPE_ID --digest DIGEST --json` and cap `pages.max_fetches_per_run`
51
- from it. Declare only what the source's own address proves, and name the `resync_only`
52
- sources and the reason in the build message.
55
+ not refresh, not only the first. Its `fast_features` names each fast feature the recipe
56
+ misses, with a one-line fix. Write `table.partition` for any table no window partitions that
57
+ has a date-like column, `batch` for a per-row GET whose API takes a list of ids, and
58
+ `reference` for a bulk dump beside a window
59
+ ([fast builds](references/making-a-table-incremental.md#fast-builds-tablepartition-batch-and-reference)).
60
+ Put them in the first revision, because each later change costs one resync. Adopting
61
+ `table.partition` on an existing table costs one full resync, so for a paid source ask the
62
+ user before that resync, as the brief and forecast rules require. Before any
63
+ `--full` of a paid collection, run `mr-data recipe forecast RECIPE_ID --digest DIGEST --json`
64
+ and cap `pages.max_fetches_per_run` from it. Declare only what the source's own address
65
+ proves, and name the `resync_only` sources and the reason in the build message.
53
66
  6. Read [build sizing](references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md).
54
67
  Use `--full` for the requested build: it is progressive by default, with a five-minute
55
68
  inspection checkpoint while the same acquisition continues. Choose bounds from measured
56
69
  source size; row and byte clamps apply per source, not elapsed time. Use explicit `--sample`
57
70
  only for a deliberate standalone inspection. Preserve the agreed semantics and quality checks.
58
- 7. Follow the returned run with `mr-data watch RUN_ID --json`. Read `run.status`: returning an
71
+ 7. Follow the returned run with `mr-data watch RUN_ID --json`. Its `source_rates` and
72
+ `mr-data status RUN_ID --json` on a live run give each collection source's rate and time left.
73
+ Quote those numbers rather than doing the arithmetic. Read `run.status`: returning an
59
74
  existing run does not mean new work was queued. Resume the known run after an interruption,
60
75
  using the saved cursor where available. Read [run lifecycle](references/live-run.md) for
61
76
  holds and continuation commands. Confirm projected spend before acquisition when required;
@@ -1,5 +1,12 @@
1
1
  ### 1. Open — the page, and the link to it, in the first message
2
2
 
3
+ **If the user handed you a `dataset_id` with the request, the page already exists and is open in
4
+ their browser.** Skip this stage's create and do not open anything: hold that `dataset_id`, link its
5
+ page once in the first message, and bind the recipe to it as `dataset.id`. The same holds when
6
+ `dataset create` answers `open_request_reused`: it made nothing, its `navigation.url` is the plain
7
+ page rather than a handoff, and `navigation.open_before_research` is false. If the quoted request is
8
+ a different dataset from the one you are building, run `dataset create --separate` instead.
9
+
3
10
  Create the dataset from a working title and put it in front of whoever is watching, **before any
4
11
  source lookup**. This is the first command after the preflight — nothing reads a note, searches a
5
12
  catalog, opens a provider page or fetches a URL until it has returned:
@@ -17,10 +17,14 @@ mr-data dataset activity DATASET_ID --phase drafting \
17
17
  The message is one plain line about what you are doing now, and the facts are counts you already
18
18
  hold. A fact you have not counted is a fact to leave out.
19
19
 
20
- **Then stop writing it by hand.** Every later phase comes from the commands themselves:
21
- registering a recipe, starting a run, reading a finished one, asking a question and promoting a
22
- table each move the page on their own. Stage 3 begins before anything is submitted, which is why
23
- it is the one stretch that has to say so by hand.
20
+ **Keep writing it through the whole build.** The line is yours. Update it when your task changes
21
+ and renew it as the [agent presence lifecycle](writing-a-decision-record.md#agent-presence-lifecycle)
22
+ says. One command adds a line of its own: registering a recipe tells the page that the recipe is
23
+ being written, and then that it is registered. Each of those is held back while the page shows a
24
+ current line you set, checked just before sending. Apart from `mr-data clarify --dataset`, which
25
+ puts your pending question on the page, no other command tells the page anything. The page shows
26
+ a run and a table going live from Studio's own record of them, and reading a finished run changes
27
+ nothing on it.
24
28
 
25
29
  **Judge the sources.** This stays with the external agent and is the substance of the task. Search
26
30
  every discovery lane with no lane priority — the sealed catalog, host public-web search, official
@@ -79,7 +83,7 @@ reason codes, only once the discovery lanes and the comparisons are complete —
79
83
  --dataset DATASET_ID` takes two identifiers rather than a question in words, and no v4 registration
80
84
  receipt returns a question identifier; it also rides the frozen `/v3/sessions` routes, which a
81
85
  deployment may have switched off. So the reading path on a hosted workspace is the ordinary loop,
82
- run early: register the recipe and take one unwindowed run.
86
+ run early: register the recipe and take one run without `--window`.
83
87
 
84
88
  **It obeys the ordinary gate.** Registering needs the brief answered or delegated, so this run
85
89
  happens AFTER stage 2's answers arrive — or at once under a delegation. What continues while you
@@ -29,9 +29,9 @@ watching; without it there is no page whose record could carry the brief, and th
29
29
  **A document that carries `dataset.id` leaves `dataset.description` out.** Registration FILLS an
30
30
  empty page's description and never replaces one, so this destroys nothing; what it does is write
31
31
  prose composed for the recipe onto the page as its documentation, from the wrong writer. The page's
32
- description is written once, below, with `mr-data dataset set DATASET_ID --description-file F`
33
- under the version it read, and the preflight refuses a document carrying both. Leave `dataset.name`
34
- in: the title is the recipe's own subject, settled below.
32
+ description is written once, below, in the one `mr-data dataset set` call that carries the title
33
+ and the tags with it, under the version it read. The preflight refuses a document carrying both.
34
+ Leave `dataset.name` in: the title is the recipe's own subject, settled below.
35
35
 
36
36
  **Registration refuses a dataset nobody was asked about**, with `THIN_BRIEF_MISSING`. The record
37
37
  has to carry either an answered `clarification` block or a delegation `decision` block first. Where
@@ -66,7 +66,23 @@ of the rights basis the sources actually grant -- for example `CC-BY-4.0`, `ODbL
66
66
  where the sources' terms are not an open licence, leave `--license` off and say so under "Source and
67
67
  rights" instead. Never invent a licence.
68
68
 
69
- While composing, send bounded snapshots with `mr-data dataset draft DATASET_ID --description-file DRAFT.md --expected-version VERSION --json`. Chain the returned version into the next snapshot, writing a meaningful paragraph at a time. These snapshots are temporary and do not replace the saved description. Commit the complete documentation with `mr-data dataset set DATASET_ID --description-file DESCRIPTION.md --expected-version VERSION --json`. If another edit moved that version, stop the draft and reconcile it with the user’s text; never retry by blindly advancing the version. Do not send fabricated partial text or delay completed text to simulate writing.
69
+ While composing, send bounded snapshots with `mr-data dataset draft DATASET_ID --description-file DRAFT.md --expected-version VERSION --json`. Chain the returned version into the next snapshot, writing a meaningful paragraph at a time. These snapshots are temporary and do not replace the saved description.
70
+
71
+ **Commit the title, the tags and the description in ONE call.** A description sent on its own can
72
+ make Studio write a new title and new tags over the ones you chose, so the final commit carries
73
+ all three:
74
+
75
+ ```sh
76
+ mr-data dataset set DATASET_ID --name "Prague airport weather history (LKPR), 2000-2026" \
77
+ --topics "weather,prague,czech republic,metar,iowa environmental mesonet" \
78
+ --description-file DESCRIPTION.md --expected-version VERSION --json
79
+ ```
80
+
81
+ Read the receipt. `fields` is what the command sent. `changed` is every field the write moved.
82
+ `written_by_studio` names each field Studio wrote itself, with its new value. If a title or tags
83
+ you chose are in it, send the three together again.
84
+
85
+ If another edit moved that version, stop the draft and reconcile it with the user’s text; never retry by blindly advancing the version. Do not send fabricated partial text or delay completed text to simulate writing.
70
86
 
71
87
  **What search engines and answer engines read.** These are the three fields search engines and
72
88
  answer engines index. A search engine (Google, Bing) ranks the page by the words a person types.
@@ -29,6 +29,25 @@ coverage and the full build's proposed coverage before requesting any missing au
29
29
  > end of the feed; the other three sources came back whole. The full build would cover 2000 to 2026
30
30
  > for all four. Run it?
31
31
 
32
+ **A sample of a source with a request window reads its latest days only.** The run asks that
33
+ source for the latest `lookback_seconds`: rounded up to whole days, at least one day, ending at the
34
+ end of the current UTC day, and never starting before `start_at`. With `lookback_seconds` of
35
+ 604800 that is the last seven days, whatever `--max-rows` says. A sample that starts after
36
+ `start_at` records `truncated: true` on that source. A check on the whole table, such as a
37
+ `row_expectation` floor, can then fail with `CHECK_FAILED` although the recipe and the source are
38
+ sound. Registration names each such source and its span under `warnings`. A full run is what
39
+ verifies a check on the whole table, once the full build is authorized.
40
+
41
+ **A source that also declares `refresh_readiness` is read only up to its last completed day.**
42
+ That leaves out the current UTC day, and the day before it until `additional_lag_seconds` of the
43
+ current day have passed. With `lookback_seconds` of 604800 and no lag a sample reads six days.
44
+ When no completed day is left, which is always so for `lookback_seconds` of 86400 or less, a
45
+ sample without `--window` does not run: it is refused `WINDOW_NOT_READY`, or waits in the queue
46
+ until Studio fails it. Registration says so under `warnings`, so read them before you start a
47
+ sample. A full run reads such a source from its `start_at`. A source whose `start_at` is the
48
+ current UTC day has no completed day until that day is over, so neither a sample nor a full run of
49
+ it reads anything before then.
50
+
32
51
  If existing authorization or a stage-2 delegation covers this full build and its spend, do not
33
52
  ask again: state the projection and run it. A narrower delegation does not authorize the full run.
34
53
 
@@ -48,6 +48,14 @@ question actually asked: `mr-data query` opens a session for the run's own datas
48
48
  is warm, waits, asks, and closes the session behind the answer. Pass `--no-session` to be refused
49
49
  instead, and wait through the window the refusal names rather than retrying in a tight loop.
50
50
 
51
+ **A query sent straight after another may find no worker to start.** The worker behind the query
52
+ before is still winding down, and Studio fails the new session at once with
53
+ `RESEARCH_SESSION_WORKER_UNAVAILABLE`. `mr-data query` waits 8 seconds and opens one more session,
54
+ once. The answer then names the first session under `session.retried_after`. If the second has no
55
+ worker either, the command refuses `THIN_RESEARCH_SESSION_ENDED` and names both sessions, each with
56
+ its state, close reason and failure code. Nothing was queued, so running the same command again
57
+ repeats nothing.
58
+
51
59
  **The statement gate is a word-boundary scan over a masked copy of the statement and does not read
52
60
  SQL.** A column literally named `system`, `copy`, `set`, `load`, `call`, `glob`, `import` or
53
61
  `export` is refused by name even in an obviously harmless statement; quote the identifier or
@@ -9,9 +9,9 @@ gap rather than doing anything, and `export-hosted-candidate`, which is a backen
9
9
  | `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. |
10
10
  | `mr-data whoami` | Use the compatibility alias for the remotely validated `auth status` answer. |
11
11
  | `mr-data clarify` | Check whether clarification is allowed. Use `--attended` for an active user chat and `--unattended` for a scheduled invocation; an explicit operator veto such as `MOSTLYRIGHT_ATTENDED=0` still wins. A non-TTY subprocess does not prove the user is absent. The command does not ask the person in chat or wait for a reply; the agent must do that. It exits non-zero when nobody can answer or the recipe is already registered. |
12
- | `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 one unwindowed run under `--max-rows`, then `peek`, `query` and `receipt`. |
12
+ | `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 one run without `--window` under `--max-rows`, then `peek`, `query` and `receipt`. |
13
13
  | `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. |
14
- | `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; a saved write whose public sync fails exits 2 and reports `public_projection_synced: false` — use `dataset sync ID` to retry that sync without rewriting Studio; `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. |
14
+ | `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`, unless the user's own request already has an empty dataset open, in which case it answers `open_request_reused` with that `dataset_id` and mints nothing (`--separate` mints a new one for a different dataset); `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; send `--name` and `--topics` whenever you send `--description-file`, because a description sent on its own can make Studio write a new title and new tags, and read `changed` and `written_by_studio` on the receipt for every field the write moved; a saved write whose public sync fails exits 2 and reports `public_projection_synced: false` — use `dataset sync ID` to retry that sync without rewriting Studio; `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. |
15
15
  | `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. The document is read first and every fault comes back in one refusal, each with the JSON pointer that names it; `--no-lint` sends it as written instead. Registration refuses `THIN_BRIEF_MISSING` when the dataset's record carries no answered question and no delegation, and `THIN_RECIPE_DATASET_UNBOUND` when the document names no `dataset.id`; `--delegated "their words"` writes the delegation onto the dataset and registers in the same command, and clears the first of those two and never the second. `mr-data recipe show ID` reads one back. `mr-data recipe explain FILE` (or a recipe identifier) lists every reason its table can or cannot refresh, each with a rule code and Studio's suggested repair, and exits 2 when one blocks it. `mr-data recipe forecast RECIPE_ID --digest DIGEST` says what one run would ask of each source before it runs, and exits 3 when the run would overrun today's platform quota or one attempt. `mr-data recipe readiness --table TABLE_ID --current-only` reads the revision one table runs now. [Making a table incremental](making-a-table-incremental.md#the-loop-explain-forecast-run-diagnose) says when to use each. |
16
16
  | `mr-data cover` | Generate and attach one branded 1200×630 dataset cover. Give it the dataset ID and what the image should depict; Studio fixes the model, single-color style, curated random palette, dimensions and storage. |
17
17
  | `mr-data run` | Start one run against a registered recipe, named as `--recipe RECIPE_ID --digest RECIPE_DIGEST` — both required, neither positional, and the digest is the bare hex the registration receipt printed. The mode is one of six: `sample`, `full`, `refresh`, `backfill`, `compact` and `replay`. Four have a shorthand flag (`--sample`, `--full`, `--refresh`, `--backfill`) and two do not, so write `--mode MODE`, which is accepted for every one of them. A refresh executes only Studio's persisted source-action plan, and a plan shape it does not refresh returns `RESYNC_REQUIRED` before acquisition; [which plans refresh](making-a-table-incremental.md#which-plans-refresh) lists them, and `mr-data recipe explain` says which one a table is. Use `mr-data table resync TABLE_ID --request-id UUID` for an explicit full source reread, retaining that UUID if the response is uncertain. `--backfill` states the exact window with `--window START END`. `--mode replay --sources-from RUN_ID` asks Studio to run the registered revision against the RETAINED RAW INPUTS of one named successful run of the same table: nothing is fetched from this computer, nothing is acquired again, the result is compared against that run and never becomes the live version, and it neither asks for nor records an approval. Where Studio has replay switched off, or the named run is not successful, not the same table, or no longer retains its inputs, it answers a typed refusal — report the code rather than retrying in another mode. `--sources-from` on any other mode is refused `THIN_ARGUMENT_INVALID` before anything is sent. `--max-rows` and `--max-source-bytes` bound ONE SOURCE rather than the finished table. A large run is held for a spend confirmation, which `--confirm` settles and whose printed `confirm_command` re-states every argument the request carried. `--approve-full RUN_ID` releases the full an existing legacy sample-first pair is holding once its preview has succeeded, naming either half of the pair; where the deployment still wants a person at a browser it answers `THIN_INTERACTIVE_HUMAN_REQUIRED` and names the run page. `--retry RUN_ID` tries one failed run again when its triple carries `room_fault: true`, re-stating that run's own coordinate under a byte ceiling that cannot narrow it, and refuses with the triple when the recipe was at fault. `--cancel RUN` stops one. |
@@ -83,6 +83,12 @@ the same code and starts no run. Only time clears it, and the refused run's rang
83
83
  new run is needed once the day is over. Report the code and wait rather than retrying or revising
84
84
  the recipe.
85
85
 
86
+ **A sample of such a source can find no completed day too.** It reads only the completed days
87
+ inside its lookback, and with `lookback_seconds` of 86400 or less there is never one. A sample
88
+ without `--window` is then refused `WINDOW_NOT_READY`, or waits in the queue until Studio fails
89
+ it. Registration names the source under `warnings`, and
90
+ [stage 6](6-build-one-run-sized-to-acquire-every-measured-source-whole.md) has the rule.
91
+
86
92
  **A refresh on the one-windowed-source bounded route seals no `receipt`.** `mr-data receipt`
87
93
  answers `THIN_NO_MATCHING_ARTIFACT` and names what the run did seal, which is the ordinary shape
88
94
  of that route rather than a fault. Read `mr-data status RUN_ID --json` instead: its `coverage`
@@ -72,8 +72,10 @@ covers nothing and says so. A `succeeded` one reports what `coverage.truncated`
72
72
  else states the bound it ran under — a `sample` its ceilings, a `full` the whole dataset, and a
73
73
  `full` at `awaiting_sample_approval` the whole dataset plus the preview it waits on. The mode
74
74
  alone never establishes a truncation, which is the rule reference 6 states: a sample sized above
75
- every measured source cut nothing and is the build. It is absent on `refresh`, `backfill`,
76
- `compact` and `replay`, and it is never evidence that a run is executing; `status` is.
75
+ every measured source cut nothing and is the build. A source with a request window is the
76
+ exception: a sample reads its latest lookback span only, and the sentence says so. It is absent
77
+ on `refresh`, `backfill`, `compact` and `replay`, and it is never evidence that a run is
78
+ executing; `status` is.
77
79
 
78
80
  **A failed run always carries the same three members** — `failure_code`, `failure_detail` and
79
81
  `failed_stage` — on three surfaces: the run record, the terminal event on the stream, and every
@@ -20,15 +20,26 @@ after every revision.
20
20
  `suggestions`. After the first full, run `mr-data recipe explain RECIPE_ID --json` again: the
21
21
  table route also judges the predecessor. The command exits 0 when the table is ready to refresh
22
22
  and 2 when a finding blocks it. An advisory finding (`blocks_refresh: false`) never changes the
23
- exit code.
23
+ exit code. The receipt's `fast_features` says which of `table.partition`, `batch` and
24
+ `reference` the recipe uses or misses, and which sources every run reads whole. Each missed
25
+ feature carries a one-line fix. `fast_features_missed` lists them for a script. See
26
+ [Fast builds](#fast-builds-tablepartition-batch-and-reference).
24
27
  2. `mr-data recipe forecast RECIPE_ID --digest DIGEST --json` before any `--full` that pays for
25
28
  calls. It sends nothing. Per source, it reports the request ceiling the document allows, the
26
29
  count the last run of the same source definition actually sent, the pace, and the runtime at
27
30
  the ceiling. For a platform credential it also reports today's remaining quota. It exits 3 on
28
31
  `PLATFORM_SPEND_EXCEEDS_QUOTA` or `RUNTIME_EXCEEDS_ONE_ATTEMPT`. Stop there and resize the
29
- collection rather than start the run.
32
+ collection rather than start the run. The forecast also reads observed limits. Each source
33
+ with a history run gets `observed`: the rate that run actually reached and the runtime of the
34
+ expected requests at that rate. A line flags a run that went under half its declared pace,
35
+ because some other limit held it. On a Studio without the forecast route, a platform source is
36
+ paced at its binding's stated rate and provider limits. `--no-observed` skips both reads.
30
37
  3. `mr-data run --recipe RECIPE_ID --digest DIGEST --full --json`, followed as
31
- [the run lifecycle](live-run.md) describes.
38
+ [the run lifecycle](live-run.md) describes. `mr-data watch RUN_ID` prints a `rate` line for
39
+ each collection source about once a minute: members done, members a minute, and the time left
40
+ once discovery has listed everything. `mr-data status RUN_ID --json` on a live run reports the
41
+ same under `source_rates`. The rate counts saved answers replayed, so it starts fast and
42
+ settles on the sending rate.
32
43
  4. `mr-data status RUN_ID --diagnose --json` when a run failed, stalled or cost more than the
33
44
  forecast said. It combines the run, its progress, its checkpoints and every attempt Studio
34
45
  recorded, and a per-row source's plan counts: upstream rows, distinct requests, answers
@@ -62,6 +73,10 @@ primitives, plus `rebuild` for a table where none of them applies.
62
73
  bytes the predecessor sealed. If the publisher later restates that period, the table keeps
63
74
  serving the old bytes, no run fails, and only an explicit resync repairs it. Never mark the
64
75
  current period closed. That drops new data outright, which is a worse mistake.
76
+ **Is it a bulk dump the publisher republishes, beside a request window?** State
77
+ `reference: {"recheck_seconds": N}` instead. A refresh reuses it like a closed source, and the
78
+ first scheduled refresh N seconds after its last read reads it whole again. See
79
+ [reference](#reference-a-bulk-dump-beside-a-window).
65
80
  3. **Does the address carry a date range, with both ends?** Declare a `window` with a `request`.
66
81
  A refresh asks again for the recent range inside `lookback_seconds`, so it picks up corrections
67
82
  on its own. This is the one continuation that makes a table refresh on its own.
@@ -85,6 +100,7 @@ Studio records the answer as one classification per source:
85
100
  | --- | --- | --- |
86
101
  | `source_class: "stream"` | `recorded` | `recorded` |
87
102
  | `closed: true` | `closed` | `reuse_predecessor` |
103
+ | `reference` | `closed` | `reuse_predecessor`, and a whole read when its recheck is due |
88
104
  | a `collection` | `collection` | `acquire_incremental` |
89
105
  | a `window` carrying a `request` | `window` | `acquire_incremental` |
90
106
  | none of those | `resync_only` | `resync_required` |
@@ -144,9 +160,10 @@ Declaring every source is the first gate. The shape the declarations form is the
144
160
 
145
161
  - **Every source a recorded stream.** The refresh appends sealed batches.
146
162
  - **At least one `window` with a `request`.** The refresh replaces the bounded partitions the
147
- window asked for. Beside it the plan may hold `closed` siblings, which reuse their sealed bytes.
148
- Or it may hold per-row request collections that carry the window's partition column. It may not
149
- hold both.
163
+ window asked for. Beside it the plan may hold `closed` or `reference` siblings, which reuse their
164
+ sealed bytes. It may also hold per-row request collections that carry the window's partition
165
+ column. Since ADR 0061 it may hold both. No per-row collection may derive its rows
166
+ from a `closed` or `reference` source.
150
167
 
151
168
  The second family is necessary, not sufficient. A plan of more than one source (one window and one
152
169
  `closed` sibling is already two) writes one table manifest. So every window must agree on
@@ -174,7 +191,7 @@ source and the pointer to change.
174
191
  | `TABLE_FORMAT_UNAVAILABLE` | The workspace writes an older table format. | `wait_for_platform`. |
175
192
  | `SOURCE_READS_WHOLE` | A source states no continuation, so a refresh would read it whole. | `declare_request_window` (its `candidate_parameters` name the address's date pairs), `mark_closed`, or `declare_rebuild`. |
176
193
  | `COLLECTION_UPSTREAM_NOT_DIRECT_WINDOW` | A collection is not a per-row request over a direct request window. | `carry_partition_column`, `make_upstream_a_request_window`, `split_table`, or `declare_rebuild`. |
177
- | `CLOSED_SIBLING_OF_DERIVED_COLLECTION` | A `closed` source sits beside a per-row collection. | `split_table`, or `declare_rebuild`. |
194
+ | `CLOSED_SIBLING_OF_DERIVED_COLLECTION` | No longer emitted. Since ADR 0061 Studio restores a closed sibling beside a per-row collection. Never split a table over it. An older Studio may still name it. | None. Upgrade the Studio rather than the recipe. |
178
195
  | `RECORDED_MIXED_WITH_WINDOW` | A recorded stream sits beside a window. | `split_table`: keep the stream in its own table. |
179
196
  | `NO_PARTITION_DELTA` | No source is a window, so there is no partition to replace. | `platform_gap`, the collection repairs above, or `declare_rebuild`. |
180
197
  | `DIRECT_WINDOW_HISTORY_PROOF_MISSING` | A lone window's predecessor holds no source-history record. | `resync_once`. |
@@ -233,6 +250,7 @@ prints that too ("retry 1/3 at T"). `mr-data reschedule` on a table that cannot
233
250
  | --- | --- |
234
251
  | recorded stream | nothing: it reads sealed batches the recorder already paid for |
235
252
  | `closed` | nothing: it reuses the predecessor's sealed bytes |
253
+ | `reference` | nothing on most refreshes, and one whole read once `recheck_seconds` have passed |
236
254
  | `window.request` | the new days plus `lookback_seconds`, per source |
237
255
  | per-row request collection | one request per upstream row in the refreshed partitions whose answer is not already held |
238
256
  | `collection` | today, only a full or resync reads it, and it reads the whole corpus |
@@ -250,6 +268,76 @@ plus a quarter. A cap near the real count makes a corpus that suddenly doubles f
250
268
  `pages.retries`. With no history the forecast says `EXPECTED_UNKNOWN`. Build a small `--sample`
251
269
  first, read its plan counts with `mr-data status RUN_ID --diagnose --json`, and size from them.
252
270
 
271
+ ### Fast builds: `table.partition`, `batch` and `reference`
272
+
273
+ `mr-data recipe explain` lists each of these under `fast_features`. Each is a recipe member, so
274
+ adding one changes the digest and the table's next full is a resync. Make every change in one
275
+ revision so the table pays once. Adopting `table.partition` on an existing table costs one full
276
+ resync. For a paid source, ask the user before that resync, as the brief and forecast rules
277
+ require. All three need Studio 4.17. An older Studio that serves the explanation route refuses
278
+ each of them at explain and registration with a 422. A Studio before 4.14 has no explanation
279
+ route, and `mr-data recipe` refuses `batch` locally with `THIN_GRAMMAR_NOT_SERVED`.
280
+
281
+ #### `table.partition`: keep unchanged partitions
282
+
283
+ Write `table.partition` for any table that no window partitions and that has a date-like column.
284
+ Collection tables, per-row tables and closed-only tables are the usual cases.
285
+
286
+ ```json
287
+ "partition": {"column": "race_date", "encoding": "iso_date_prefix"}
288
+ ```
289
+
290
+ It sits in `table`, beside `columns`.
291
+
292
+ - `column` is a declared table column, and every row needs a value in it. Check
293
+ `count(*) where race_date is null` is 0 before you write it.
294
+ - `encoding` is `iso_date_value` for a `date` column, `iso_date_prefix` for a timestamp or an
295
+ ISO string, `iso_month_prefix` for a calendar month, or `value` for a low-cardinality key.
296
+ - A table holds at most 4,096 parts. A daily key over more than about 11 years overflows it. Use
297
+ `iso_month_prefix` for a longer span.
298
+ - A full run keeps each partition whose bytes did not change by reference, and uploads only the
299
+ rest. The first full after you add it is the one resync, and it uploads everything once.
300
+ - It does not make a refresh admissible. A table that could not refresh still cannot.
301
+ - A window already partitions its table. Beside a window, `table.partition` must state the same
302
+ column and key, so leave it out. A table a stream writes cannot declare one.
303
+
304
+ #### `batch`: many rows in one request
305
+
306
+ A per-row GET whose API takes a list of ids in one parameter can send up to 100 members in one
307
+ call:
308
+
309
+ ```json
310
+ "request": {
311
+ "method": "GET",
312
+ "static_query": [{"name": "ids", "value": "{id}"}],
313
+ "parameters": [{"name": "id", "type": "string", "required": true}],
314
+ "batch": {"parameter": "id", "separator": ",", "max_rows": 50, "key_column": "movie_id"}
315
+ }
316
+ ```
317
+
318
+ - `parameter` is declared with `required: true`. `{id}` is the whole of one `static_query` value
319
+ and appears nowhere else.
320
+ - `key_column` is a column the pinned Reader declares. The worker splits the answer back to
321
+ members by that column.
322
+ - `batch` refuses a POST, a `body_template`, a `platform` credential and
323
+ `pages.max_unavailable_members` above 0. The preflight names each before anything is sent.
324
+ - Member ids and saved answers do not change, so turning `batch` on keeps every paid answer.
325
+ - `absent: "failed"` fails a member the answer does not name, so the next run asks again. The
326
+ default, `empty`, records it with no rows.
327
+
328
+ #### `reference`: a bulk dump beside a window
329
+
330
+ ```json
331
+ {"name": "genres", "reference": {"recheck_seconds": 86400}, "limits": {"max_source_bytes": 50000000}}
332
+ ```
333
+
334
+ - `recheck_seconds` is from 3,600 to 31,536,000.
335
+ - A reference is never `closed`, never carries a `window` or a `collection`, and is never a
336
+ stream. No per-row collection may derive its rows from it.
337
+ - A refresh reuses its sealed bytes. Once `recheck_seconds` have passed, one scheduled refresh
338
+ reads it whole. The same digest changes nothing. A new digest makes that refresh recompute the
339
+ whole table, so every version joins exactly one edition of the dump.
340
+
253
341
  ### Probing a request window without an executing probe
254
342
 
255
343
  `mr-data recipe explain` suggests `declare_request_window` for a source that reads whole. Its
@@ -38,8 +38,8 @@ or none: there is no other place a Reader is named. Read the pin grammar and the
38
38
  **No command tries a Reader against a URL.** `mr-data peek` reads the preview one hosted run sealed,
39
39
  and its `--reader`, `--reader-options`, `--format` and `--json-*` flags are accepted and print `no
40
40
  effect here; the hosted preview arrives already decoded`. A Reader is settled the way every other
41
- part of the document is settled: pin the coordinate in the recipe, register it, take an unwindowed
42
- run under `--max-rows`, and read the columns back with `peek` and the decode evidence with
41
+ part of the document is settled: pin the coordinate in the recipe, register it, take a run without
42
+ `--window` under `--max-rows`, and read the columns back with `peek` and the decode evidence with
43
43
  `receipt`. A wrong coordinate or a wrong settings object is a typed refusal on that run naming the
44
44
  source, which is the answer — change the pin and register the revision.
45
45
 
@@ -54,8 +54,11 @@ double quotes.
54
54
 
55
55
  Two things are currently broken here and are not worth diagnosing again:
56
56
 
57
- - Aggregating a timestamp column fails: `min(obs_time)` answers `Required module 'pytz' failed to
58
- import`, because the query image lacks it. Aggregate the rendered value instead —
59
- `min(CAST(obs_time AS VARCHAR))`.
57
+ - Any answer that carries a timestamp with a time zone fails, aggregate or not. `select obs_time`,
58
+ `min(obs_time)` and `max(obs_time)` all answer `Required module 'pytz' failed to import`,
59
+ because the query image lacks it. Cast the result to text in the statement:
60
+ `CAST(max(obs_time) AS VARCHAR)`. Never write `max(CAST(obs_time AS VARCHAR))`, which takes the
61
+ maximum of text and compares characters, not moments. Studio may answer an identical statement
62
+ with its stored failure. The cast makes a new statement, so it always runs.
60
63
  - `mr-data receipt` on a run that succeeded answers `THIN_STUDIO_SCHEMA_VALIDATION_FAILED`. Read
61
64
  `mr-data status --json` for coverage and `mr-data download --kind receipt` for the sealed record.
@@ -21,7 +21,9 @@ are the first two acts of the session after the preflight — before the durable
21
21
  before `mr-data runs`, before a catalog is searched, before a provider's page is opened and before
22
22
  any URL is fetched. Research is what fills the page, and a page created after the research already
23
23
  happened is the assertion this stage exists to prevent. Nothing in stage 1 depends on knowing the
24
- sources: the title is a working one and stage 5 settles it.
24
+ sources: the title is a working one and stage 5 settles it. A `dataset_id` handed over with the
25
+ request, or a create that answers `open_request_reused`, is that page already open: build into it
26
+ and open nothing.
25
27
 
26
28
  **Then establish whether anybody can be asked.** When the host provides an active user
27
29
  conversation, pass `--attended`; a tool subprocess without a terminal is not proof that the user
@@ -56,9 +56,11 @@ Write it as a source whose connector is that coordinate plus a `collection` memb
56
56
  - `collection.pages` says what may be fetched and how much. It names `allowed_origins`, which
57
57
  must include the discovery address's own origin. It may state `max_pages`, `max_fetches_per_run`
58
58
  (up to 50 000), `concurrency` (up to 10 000), `request_timeout_seconds`, `retries` and a
59
- `revisit` rule. Its pace is `min_interval_milliseconds` in whole milliseconds, 16 or more and
60
- 1000 when left out, or the legacy whole-second `min_interval_seconds`. Only `allowed_origins` is
61
- required. Every other member has a stated meaning when it is left out.
59
+ `revisit` rule. `retries` is 0 to 3 and 2 when left out. Registration refuses 4 or more. Its pace
60
+ is `min_interval_milliseconds` in whole milliseconds, 16 or more and 1000 when left out, or the
61
+ legacy whole-second `min_interval_seconds`. Leaving the pace out means one request a second. State
62
+ the pace the publisher allows. Only `allowed_origins` is required. Every other member has a
63
+ stated meaning when it is left out.
62
64
  - `limits.max_requests` is REQUIRED and bounds every request the run makes, discovery and detail
63
65
  pages together. `limits.max_source_bytes` bounds the fetched bytes; `limits.max_rows` clamps the
64
66
  merged relation.
@@ -235,6 +237,11 @@ with every byte the source has left, up to the 16 MiB one answer may take. With
235
237
  at 0 there is no second attempt, so the first answer too large fails the run. Raise
236
238
  `limits.max_source_bytes`, or `pages.retries` when it is 0. Neither helps an answer over 16 MiB.
237
239
 
240
+ If the API takes a list of ids in one query parameter, add `request.batch` and send up to 100
241
+ members in one call. It keeps every member id and saved answer. It refuses a POST, a
242
+ `body_template`, a `platform` credential and a failure budget above 0.
243
+ [`batch`](making-a-table-incremental.md#batch-many-rows-in-one-request) has the grammar.
244
+
238
245
  A collection whose calls are paid for is sized from evidence, not from its ceiling. Run
239
246
  `mr-data recipe forecast RECIPE_ID --digest DIGEST --json` before any `--full`, and cap
240
247
  `pages.max_fetches_per_run` at the suggestion it prints. A cap near the real count refuses a corpus
@@ -260,7 +267,7 @@ sends requests only for rows in the refreshed partitions. A request the predeces
260
267
  successfully is never sent again, whatever the row's carried cells or partition now are. A full
261
268
  build leaves out an upstream row whose partition cell does not read. Read this source alone in the
262
269
  transform, and take upstream values from the carried columns. A transform that joins it back to
263
- its upstream cannot refresh incrementally. Keep `closed` sources out of the same table. A plan with
264
- a per-row request collection beside a closed source is `RESYNC_REQUIRED`, although the table still
265
- registers and builds. [Which plans refresh](making-a-table-incremental.md#which-plans-refresh) has
266
- the rest, and the YC worked example there splits such a table in two.
270
+ its upstream cannot refresh incrementally. A `closed` or `reference` source may sit in the same
271
+ table, and a refresh restores it from the predecessor (ADR 0061). Do not split a table
272
+ to separate them. No per-row collection may derive its rows from a `closed` or `reference` source.
273
+ [Which plans refresh](making-a-table-incremental.md#which-plans-refresh) has the rest.