mostlyright-data 0.31.7__tar.gz → 0.31.8__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 (139) hide show
  1. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/PKG-INFO +1 -1
  2. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/SKILL.md +24 -6
  3. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/commands.md +2 -2
  4. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/promote.md +7 -0
  5. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/delivery.py +213 -17
  6. mostlyright_data-0.31.8/src/mostlyright/data_harness/thin/table_view.py +384 -0
  7. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_tables.py +71 -9
  8. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/vocabulary.py +2 -1
  9. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/render.py +3 -0
  10. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/.gitignore +0 -0
  11. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/README.md +0 -0
  12. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/pyproject.toml +0 -0
  13. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/scripts/hatch_build.py +0 -0
  14. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/agents/openai.yaml +0 -0
  15. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +0 -0
  16. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
  17. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
  18. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
  19. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
  20. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
  21. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
  22. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
  23. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
  24. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/agent-protocol.md +0 -0
  25. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
  26. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
  27. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/boundaries.md +0 -0
  28. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
  29. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
  30. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/enriching-with-small-models.md +0 -0
  31. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/installation-parity.md +0 -0
  32. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/live-run.md +0 -0
  33. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/making-a-table-incremental.md +0 -0
  34. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/narrating-the-run.md +0 -0
  35. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
  36. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/one-install.md +0 -0
  37. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/patterns.md +0 -0
  38. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/prediction-labels.md +0 -0
  39. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/preparing-table-search.md +0 -0
  40. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/readers.md +0 -0
  41. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/receipts.md +0 -0
  42. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/recipe-test.md +0 -0
  43. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
  44. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
  45. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/reference-pages.md +0 -0
  46. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/required-protocol.md +0 -0
  47. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/source-credentials.md +0 -0
  48. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/sources.md +0 -0
  49. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
  50. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/transforms.md +0 -0
  51. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/user-communication-contract.md +0 -0
  52. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
  53. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  54. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/__init__.py +0 -0
  55. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/agent_protocol.py +0 -0
  56. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/canonical.py +0 -0
  57. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/formats.py +0 -0
  58. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  59. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/key_seam.py +0 -0
  60. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/page_coverage.py +0 -0
  61. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  62. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/session_probes.py +0 -0
  63. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/skill_assets.py +0 -0
  64. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/table_manifest.py +0 -0
  65. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  66. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  67. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  68. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/activity.py +0 -0
  69. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  70. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/backoff.py +0 -0
  71. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/categories.py +0 -0
  72. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/commands.py +0 -0
  73. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  74. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/description_shape.py +0 -0
  75. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/download.py +0 -0
  76. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/enrichment.py +0 -0
  77. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
  78. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/matching_benchmark.py +0 -0
  79. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  80. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/parity.py +0 -0
  81. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/probe.py +0 -0
  82. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
  83. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  84. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/propose.py +0 -0
  85. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe.py +0 -0
  86. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  87. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_explain.py +0 -0
  88. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
  89. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
  90. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_test.py +0 -0
  91. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
  92. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/release_check.py +0 -0
  93. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/research.py +0 -0
  94. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/router.py +0 -0
  95. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
  96. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
  97. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/runs.py +0 -0
  98. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/session.py +0 -0
  99. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/stream.py +0 -0
  100. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  101. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/studio_capabilities.py +0 -0
  102. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/support.py +0 -0
  103. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/table_search_contract.json +0 -0
  104. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/transport.py +0 -0
  105. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  106. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4.py +0 -0
  107. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
  108. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  109. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
  110. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  111. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  112. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
  113. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  114. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_matching.py +0 -0
  115. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  116. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
  117. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  118. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
  119. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
  120. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_search.py +0 -0
  121. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
  122. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  123. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
  124. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  125. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  126. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  127. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  128. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  129. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  130. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  131. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  132. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  133. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  134. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  135. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  136. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/login.py +0 -0
  137. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  138. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  139. {mostlyright_data-0.31.7 → mostlyright_data-0.31.8}/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.31.7
3
+ Version: 0.31.8
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/
@@ -14,6 +14,15 @@ offer the small models (classify, extract, match) for the judgement no rule can
14
14
  wait for the person to name a model. Steps 3, 4, 5, 8 and 10 say where, and
15
15
  [enriching with small models](references/enriching-with-small-models.md) says how.
16
16
 
17
+ ## Set the goal as a checklist
18
+
19
+ At the start of every build, write the goal as a checklist with the host's todo or plan tool:
20
+ TodoWrite in Claude Code, todowrite in opencode, or the plan tool where the host has one. List
21
+ each deliverable the brief names. Then list every delivery step: the description in the skill's
22
+ shape, the cover, search on the main table, the table view review, the refresh cadence and the
23
+ enrichment offer. Keep the list current as you work. Each item ends done, or skipped on purpose
24
+ with the reason written beside it. Do not report the build as done while an item is open.
25
+
17
26
  ## Four gates
18
27
 
19
28
  Every build passes these four gates in order. Do not skip one. The numbered steps below carry
@@ -196,9 +205,11 @@ step 6. The stage 3 reference holds its rule. Gate 4 is step 6.
196
205
 
197
206
  ## Work in parallel
198
207
 
199
- When the host can run subagents or background commands, give independent work to them at the
200
- same time instead of doing it one item after another. The lead agent keeps the brief, every
201
- decision, every write and every word to the user.
208
+ When the host can run subagents, use them. The discovery lanes in step 4 and the probes of the
209
+ shortlisted sources must fan out: one subagent per lane and one per source, all started at the
210
+ same time. Run them one after another only when there is one lane or one source. Give other
211
+ independent work to subagents or background commands the same way. The lead agent keeps the
212
+ brief, every decision, every write and every word to the user.
202
213
 
203
214
  - **What fans out.** The discovery lanes in step 4, one subagent per lane. Probing the sources
204
215
  on the shortlist, one subagent per source, each with `mr-data source inspect`. Bounded gate-3
@@ -272,10 +283,17 @@ merely to obtain a green run. Read [recovery details](references/agent-protocol.
272
283
  serves that model. Confirm each lead against the rows before you offer it.
273
284
  A succeeded full run's `mr-data status` and `mr-data watch` output carries a `delivery` block.
274
285
  It names a missing cover, a description that is missing or out of the skill's shape,
275
- natural-language search when the run's table has none, and always the enrichment offer.
286
+ natural-language search when the run's table has none, a view review when the table has no
287
+ saved view, a refresh cadence when none is recorded, and always the enrichment offer.
276
288
  Complete every step before you report the build as done. Studio marks no main table, so you
277
- judge it: skip the search step for a supporting table and say so in one line. A scratch table
278
- you test and demote owes none of these steps.
289
+ judge it: skip the search and view steps for a supporting table and say so in one line. A
290
+ scratch table you test and demote owes none of these steps.
291
+ Review the view with `mr-data table view TABLE_ID --json`. It lists the view kinds this
292
+ deployment offers and the roles each one binds. Choose from the columns and a bounded sample,
293
+ check the choice with `--set VIEW.json --dry-run`, then save it. The plain table is fine when
294
+ it reads best, but save it as a deliberate choice.
295
+ Record the cadence under step 9's readiness rule. A fixed snapshot has no cadence to record:
296
+ write that decision as a dataset note and skip the step, saying so in one line.
279
297
  Offering enrichment is mandatory. Accepting it is the person's choice, and a no is a complete
280
298
  answer.
281
299
  9. Any truncated source means a preview, not the requested complete dataset. Inspect coverage
@@ -26,7 +26,7 @@ run in full.
26
26
  | `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. A recipe that misses a feature that sets how fast a run fetches (a stated `pages.concurrency` under 64, a per-row request open to `batch`, a page walk open to `pagination.parallel`, or a Parquet file open to a ranged read) is refused `THIN_RECIPE_SLOW`, with each feature's pointer and fix, before anything is sent. Apply the fixes, or pass `--accept-slow "REASON"` with one sentence on why the slow path is right for this source. The receipt prints the reason and the features it covered. 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` 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. `--digest` is optional there, and without it the digest the recipe was registered with is forecast. `mr-data recipe readiness --table TABLE_ID --current-only` reads the revision one table runs now. The explanation's `fast_features` names each fast feature the recipe uses or misses, with a one-line fix. [Making a table incremental](making-a-table-incremental.md#the-loop-explain-forecast-run-diagnose) says when to use each. |
27
27
  | `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. |
28
28
  | `mr-data run` | Start one run against a registered recipe, named as `--recipe RECIPE_ID`, with `--digest RECIPE_DIGEST` when you want the run to assert which bytes it means. Neither is positional. The digest is the bare hex the registration receipt printed. Left out, the run reads it back from the registered recipe, which is the same value, because a recipe identifier names one revision. A full of a recipe that misses a fetch-speed feature is refused `THIN_RECIPE_SLOW`, as registration is, unless `--accept-slow "REASON"` is passed. Other modes print the missed features and are not refused. 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. `--max-rows` cuts rows after a source is fetched and decoded, so it does not shorten a collection walk. A sample with no clamp is refused: `CLAMPS_REQUIRED` from Studio, and `THIN_REQUEST_INVALID` from this client first. A table's first succeeded run goes live, truncated or not, so test on a scratch table as [the probe rule](3-probe-read-a-source-before-committing-to-it.md#the-probe-rule-test-on-a-scratch-table-never-on-the-real-table) says. A large run is held for a size confirmation (not a charge), 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` resumes a failed or cancelled progressive full from the pages it saved (`resume_capture_run_id` and the ceilings it carried; `retry_of_run_id` as well only when Studio admits a named retry). A failure that says the recipe must change is refused before anything is posted. Any other failed run is tried again when its triple carries `room_fault: true`, re-stating that run's own coordinate and ceilings and naming the failed run so Studio starts a new attempt, and is refused with the triple and the next step Studio's recovery category names when it is not. `room_fault` is Studio's `recovery.room_fault` where Studio states one. A source that kept refusing (`SOURCE_RATE_LIMITED`, `SOURCE_HTTP_5XX`, `SOURCE_PROXY_BUSY`) is sent anyway: Studio admits it 120 seconds after the failure and refuses `RUN_RETRY_TOO_SOON` with a Retry-After before then, so wait that long and run `--retry` again. `--cancel RUN` stops one. |
29
- | `mr-data status` | Report which of the seven states one run is in, what it delivered and whether a ceiling cut it short, and — on a failure — what failed, where, and whether the fault was the execution room's rather than the recipe's. Exits non-zero on a failed run. `--diagnose` adds everything else Studio can say about the run: its progress, a per-row source's plan counts, its checkpoints, and how each attempt ended, with a lost attempt's cause marked as an inference. A succeeded full run carries `delivery`: the steps still owed before the build is done (a description missing or out of the skill's shape, a missing cover, natural-language search when the dataset's main table has none, and always the enrichment offer), each with its command. It never changes the exit code. |
29
+ | `mr-data status` | Report which of the seven states one run is in, what it delivered and whether a ceiling cut it short, and — on a failure — what failed, where, and whether the fault was the execution room's rather than the recipe's. Exits non-zero on a failed run. `--diagnose` adds everything else Studio can say about the run: its progress, a per-row source's plan counts, its checkpoints, and how each attempt ended, with a lost attempt's cause marked as an inference. A succeeded full run carries `delivery`: the steps still owed before the build is done (a description missing or out of the skill's shape, a missing cover, natural-language search when the dataset's main table has none, a view review when the table has no saved view, a refresh cadence when none is recorded, and always the enrichment offer), each with its command. A read that fails adds a note, never a step. It never changes the exit code. |
30
30
  | `mr-data runs` | Report this workspace's own runs: identifier, mode, state and creation time, plus the failure triple of any that failed and whether that failure was the execution room's. `--dataset DATASET_ID` narrows it to one dataset's runs, so "what have I run for this dataset?" is one scoped read, and a value that is not an identifier is refused `THIN_ARGUMENT_INVALID`. `--status` and `--mode` narrow it too and are refused `THIN_ARGUMENT_INVALID` for a word outside the seven states or the six modes; the three combine. Studio narrows before it cuts a page, so a filtered page holds the newest runs that match however many runs the workspace has. `--limit` says how many, and pages are followed to reach it; a listing that does not end says how many were read and suggests a smaller `--limit` or a narrower filter. The answer prints `next_cursor`, and `--cursor` continues from it. |
31
31
  | `mr-data watch` | Stream one run's live progress under the durable event type of each stage, resuming across stream cuts. Exits non-zero when the run failed. `mr-data watch RUN_ID RUN_ID ...` follows up to 16 runs at once, prints one line as each ends, and returns once all have finished with one receipt per run (`failed_run_ids`, `refused_run_ids`); it exits 2 when any run failed and 1 when a run could not be watched. `--from-seq` and `--last-event-id` resume one run only. A full run that succeeds carries the same `delivery` block as `mr-data status`, and the multi-run receipt merges every run's steps. |
32
32
  | `mr-data query` | Run one bounded read-only SQL statement over a finished run's own table and print the rows. Name the table `run_table` in the SQL, whatever the recipe called it. Bounded on rows, on answer bytes and on time, and refused by name when a bound is crossed. When no worker can answer, the refusal names `mr-data download RUN_ID` as `fallback_command`, which brings the table here to read locally. The question is asked through Mostly Right Cloud; while its usage paywall is on and the workspace is unsubscribed, each answered query counts toward the person's monthly allowance, and a spent allowance refuses `THIN_USAGE_LIMIT`. A refusal after the question was queued names its `query_id`; running the same command again reads that query back. |
@@ -47,7 +47,7 @@ run in full.
47
47
  | `mr-data open` | Mint a single-use, short-lived address that continues this session in a browser at one dashboard path, and print it. `--open` hands it to the platform opener. The credential decides who mints: a device credential — what `mr-data login` stores — mints at Cloud, and a personal access token mints at Studio. |
48
48
  | `mr-data promote` | Record the cadence one table refreshes on and the reasoning behind it. A table's first succeeded run goes live on its own, so this is not what makes it readable; it is idempotent on a table that is already live, and it is how a withdrawn table is put back. The Harness worker does not perform the catch-up or scheduled refresh; report those only when another deployed component returns durable evidence that it did. |
49
49
  | `mr-data reschedule` | Change how often one live table refreshes, without the demote and promote that would reset its bookkeeping and buy a catch-up run. `--cadence` takes a cron expression or an interval such as `every 6h`; `--why` records the reasoning. It starts no run. `--lock` freezes the schedule so Studio stops adjusting it; `--unlock` lets it follow the source again. A table that cannot refresh answers `table_schedule_held` and exits 2, naming the two repairs: declare `rebuild`, or `table resync`. |
50
- | `mr-data table` | Report what one table is serving and how its refresh schedule is going: the state in plain words, the schedule in force, the next refresh, the schedule that was asked for and the reason given for it, and — once Studio has settled on one — the interval it measured and how many source updates that rests on. Where Studio reports it, it also prints the table's refresh strategy: "rebuilds every 7d; next T" or "held: blocker". `table rename TABLE --name NAME` writes a new name, lowercase snake_case and unique within its dataset, fenced on that dataset's version and retried once if somebody else wrote first; nothing is rebuilt, because every run, version and schedule hangs off the identifier, which does not move. `table archive TABLE --confirm-name NAME` retires one table you name by the name it is called, and frees that name; a live table is refused until you `demote` it, and nothing built is deleted. `table archive TABLE [TABLE ...] --confirm DELETE` retires as many tables as you name at once: `DELETE` is the literal word, typed exactly, and it confirms the identifiers you typed rather than a name, so each table is retired under the name Studio is holding for it. Every table is attempted whatever the one before it answered, one line is printed for each, and the command exits non-zero if any was not retired — so read the outcome of each rather than the exit code alone. `--confirm-name` is refused when you name more than one table. |
50
+ | `mr-data table` | Report what one table is serving and how its refresh schedule is going: the state in plain words, the schedule in force, the next refresh, the schedule that was asked for and the reason given for it, and — once Studio has settled on one — the interval it measured and how many source updates that rests on. Where Studio reports it, it also prints the table's refresh strategy: "rebuilds every 7d; next T" or "held: blocker". `table rename TABLE --name NAME` writes a new name, lowercase snake_case and unique within its dataset, fenced on that dataset's version and retried once if somebody else wrote first; nothing is rebuilt, because every run, version and schedule hangs off the identifier, which does not move. `table archive TABLE --confirm-name NAME` retires one table you name by the name it is called, and frees that name; a live table is refused until you `demote` it, and nothing built is deleted. `table archive TABLE [TABLE ...] --confirm DELETE` retires as many tables as you name at once: `DELETE` is the literal word, typed exactly, and it confirms the identifiers you typed rather than a name, so each table is retired under the name Studio is holding for it. Every table is attempted whatever the one before it answered, one line is printed for each, and the command exits non-zero if any was not retired — so read the outcome of each rather than the exit code alone. `--confirm-name` is refused when you name more than one table. `table view TABLE` says whether a live table has a saved view (`view_saved`) and lists the view kinds (`table`, `cards`, `places`, `products`, `events`, `jobs`, `people`, `articles`) and the roles each binds. There is no map or chart kind, and `places` are location cards. `table view TABLE --set VIEW.json` saves `{"version": 1, "template": KIND, "fields": {ROLE: COLUMN}}`, with optional `facts` (up to 4 columns) and `imageStyle`. `--dry-run` has Cloud validate it against the live table and save nothing. The file is checked here before anything is sent. Replacing a saved view needs its revision as `--expected-revision`, which MCP `get_table` reports, because Cloud serves no CLI read of it. A stale revision is refused `THIN_TABLE_VIEW_VIEW_CHANGED`. The command uses the device key. |
51
51
  | `mr-data pin` | Ask Studio to freeze one table pointer on an exact version, for a rollback or a hold. |
52
52
  | `mr-data unpin` | Ask Studio to resume pointer tracking; read the returned state before claiming it resumed. |
53
53
  | `mr-data demote` | Ask Studio to withdraw the pointer and any schedule it owns, for one table or for as many as you name: `demote TABLE [TABLE ...]` withdraws them one after another, attempts every one of them whatever the one before it answered, prints a line for each, and exits non-zero if any is still live. Pair it with `table archive` when you are retiring a set: a live table cannot be archived, so it is withdraw-then-archive, two commands rather than a loop. |
@@ -55,6 +55,13 @@ simply omitting the cadence command. Do not promise that ongoing work is disable
55
55
  state proving it, and do not demote or archive a requested readable dataset as a workaround. If the
56
56
  available controls cannot preserve the requested snapshot and refresh behavior, report that gap.
57
57
 
58
+ **A fixed snapshot is a recorded decision, not a missing cadence.** Studio has no cadence that
59
+ means never. Leave the table unscheduled, confirm `mr-data table TABLE_ID` shows no schedule,
60
+ and record the decision and its reason with
61
+ `mr-data dataset note DATASET_ID --heading "Fixed snapshot: no refresh" --markdown-file NOTE.md`.
62
+ Then skip the delivery block's cadence step, saying so in one line. The step stays listed, and
63
+ the note is the record.
64
+
58
65
  This call records a schedule; it is not proof of data continuation. Schedule only a current recipe
59
66
  that `mr-data recipe readiness --table TABLE_ID --current-only --json` reports as refresh-ready, or
60
67
  one that declares `rebuild`. [Making a table incremental](making-a-table-incremental.md) says
@@ -1,9 +1,10 @@
1
1
  """The steps a succeeded full run still owes before the build is reported as done.
2
2
 
3
3
  A full run that succeeded has data on the page. The page is not finished until it has a real
4
- description and a cover, and the person has been offered enrichment. This module names what is
5
- missing, reading the dataset once, and always names the enrichment offer. It never raises: a
6
- dataset read that fails degrades to the enrichment step and a note.
4
+ description and a cover, the main table has search and a deliberately chosen view, the table's
5
+ refresh cadence is recorded, and the person has been offered enrichment. This module names what
6
+ is missing, reading the dataset, the table's search, the table and its saved view once each, and
7
+ always names the enrichment offer. It never raises: a read that fails adds a note, never a step.
7
8
  """
8
9
 
9
10
  from __future__ import annotations
@@ -14,7 +15,7 @@ from typing import Any
14
15
  from mostlyright.data_harness.thin import description_shape
15
16
  from mostlyright.data_harness.thin.session import StudioSession
16
17
  from mostlyright.data_harness.thin.transport import ThinLaneError, Transport
17
- from mostlyright.data_harness.thin.v4 import identifier
18
+ from mostlyright.data_harness.thin.v4 import GET_TABLE_PATH, identifier
18
19
  from mostlyright.data_harness.thin.v4_datasets import StudioV4DatasetClient
19
20
 
20
21
  DELIVERY_VERSION = 1
@@ -34,7 +35,14 @@ _FAILURE_SAYS: dict[str, str] = {
34
35
  }
35
36
 
36
37
  #: The step names, in the order a person does them.
37
- STEPS: tuple[str, ...] = ("description", "cover", "search", "enrichment_offer")
38
+ STEPS: tuple[str, ...] = (
39
+ "description",
40
+ "cover",
41
+ "search",
42
+ "table_view",
43
+ "cadence",
44
+ "enrichment_offer",
45
+ )
38
46
 
39
47
  _ENRICHMENT_WHY = (
40
48
  "Offering enrichment is required on every full build. Confirm each lead against the rows, "
@@ -51,6 +59,42 @@ _SEARCH_WHY = (
51
59
  "authoring, or when the table has nothing searchable."
52
60
  )
53
61
 
62
+ _VIEW_WHY = (
63
+ "Review how its rows read best and save that view. mr-data table view lists the view kinds "
64
+ "this deployment offers and the roles each one binds. Inspect the columns and a bounded "
65
+ "sample with mr-data peek, check the view with --set VIEW.json --dry-run, then save it. The "
66
+ "plain table is a valid choice when it reads best, but save it as a deliberate choice. Skip "
67
+ "it, saying why in one line, for a supporting table."
68
+ )
69
+
70
+ _SNAPSHOT = (
71
+ "Studio has no cadence that means never. When the agreed answer is a fixed snapshot, leave "
72
+ "the table unscheduled, record that decision with mr-data dataset note {dataset_id} "
73
+ '--heading "Fixed snapshot: no refresh" --markdown-file NOTE.md, and skip this step, saying '
74
+ "so in one line. The step stays listed after the note, because the note is the record."
75
+ )
76
+
77
+ _CADENCE_WHY = (
78
+ "Record it from the update answer in the brief. First confirm mr-data recipe readiness "
79
+ "--table {table_id} --current-only --json reports the current revision refresh-ready, or that "
80
+ "the recipe declares rebuild. A recipe that is not refresh-ready is a design fault to repair "
81
+ "with mr-data recipe explain, not a reason to skip this step. Then record the cadence and "
82
+ "the reasoning with promote. "
83
+ )
84
+
85
+ _NOT_READY_WHY = (
86
+ "Its current recipe is not refresh-ready: {blocker}. That is a design fault to repair, not a "
87
+ "state to wait out. Run mr-data recipe explain RECIPE_ID --json, repair the recipe, register "
88
+ "the revision and resync once, then record the cadence and the reasoning with promote. "
89
+ )
90
+
91
+ _PARKED_WHY = (
92
+ "A cadence was requested, but Studio holds it because the current recipe is not "
93
+ "refresh-ready: {blocker}. That is a design fault to repair, not a state to wait out. Run "
94
+ "mr-data recipe explain RECIPE_ID --json, repair the recipe, register the revision and resync "
95
+ "once. Studio restores the held schedule when the recipe is ready."
96
+ )
97
+
54
98
  #: Every block says this: a scratch table is a test, not the delivered dataset.
55
99
  SCOPE = "A scratch table you test and demote owes none of these steps."
56
100
 
@@ -137,7 +181,7 @@ def _search_state(answer: Mapping[str, Any], table_id: str) -> str:
137
181
 
138
182
 
139
183
  def _search_step(
140
- client: StudioV4DatasetClient, record: Mapping[str, Any], run: Mapping[str, Any]
184
+ client: StudioV4DatasetClient, table_id: str
141
185
  ) -> tuple[dict[str, str] | None, str | None]:
142
186
  """The search step when the run's table has no search, and any note. Never raises.
143
187
 
@@ -145,10 +189,6 @@ def _search_step(
145
189
  for a supporting table.
146
190
  """
147
191
 
148
- try:
149
- table_id = identifier(run.get("table_id"), "table identifier")
150
- except Exception: # a record without a usable table identifier is still a delivered run
151
- return None, "Search could not be checked: the run names no table."
152
192
  try:
153
193
  # Imported here: the search module's imports reach the run module, which imports this one.
154
194
  from mostlyright.data_harness.thin.v4_search import TABLE_SEARCH_PATH
@@ -179,6 +219,153 @@ def _search_unchecked(table_id: str, why: str) -> str:
179
219
  return f"Search could not be checked{why}. Read mr-data search status {table_id}."
180
220
 
181
221
 
222
+ def _refusal_said(error: BaseException) -> str:
223
+ return f" ({error.code})" if isinstance(error, ThinLaneError) else ""
224
+
225
+
226
+ def _table_record(
227
+ client: StudioV4DatasetClient, table_id: str
228
+ ) -> tuple[Mapping[str, Any] | None, str | None]:
229
+ """The Studio table record, or a note saying it could not be read. Never raises."""
230
+
231
+ try:
232
+ record = client._call("GET", GET_TABLE_PATH.format(table_id=table_id), expected=(200,))
233
+ except Exception as error: # a failed read must never fail the command
234
+ return None, (
235
+ f"View and cadence could not be checked{_refusal_said(error)}. "
236
+ f"Read mr-data table {table_id}."
237
+ )
238
+ if not isinstance(record, Mapping) or record.get("table_id") != table_id:
239
+ return None, f"View and cadence could not be checked. Read mr-data table {table_id}."
240
+ return record, None
241
+
242
+
243
+ def cadence_step(
244
+ record: Mapping[str, Any], *, table_id: str, dataset_id: str
245
+ ) -> dict[str, str] | None:
246
+ """The cadence step when a live table has no schedule and no cadence recorded.
247
+
248
+ Studio reports both as null until an explicit cadence is chosen, and while a readiness hold
249
+ parks a requested schedule. Studio also reads every such live table as ``held``, so ``held``
250
+ alone says nothing: a parked schedule or a named blocker is what makes it a repair.
251
+ """
252
+
253
+ if record.get("promotion_status") != "live":
254
+ return None
255
+ if record.get("schedule") is not None or record.get("cadence") is not None:
256
+ return None
257
+ strategy = record.get("refresh_strategy")
258
+ strategy = strategy if isinstance(strategy, Mapping) else {}
259
+ blocker = strategy.get("blocker") or strategy.get("blocker_code")
260
+ said = blocker if isinstance(blocker, str) and blocker else "a blocking finding"
261
+ readiness = f"mr-data recipe readiness --table {table_id} --current-only --json"
262
+ if isinstance(strategy.get("held_schedule"), Mapping):
263
+ why = (
264
+ _PARKED_WHY.format(blocker=said)
265
+ if isinstance(blocker, str) and blocker
266
+ else "A cadence was requested, and Studio has not yet restored it. Read "
267
+ f"{readiness} and mr-data table {table_id} until the schedule is in force."
268
+ )
269
+ return _step(
270
+ "cadence",
271
+ readiness,
272
+ f"This run's table, {table_id}, has no refresh cadence in force. " + why,
273
+ )
274
+ snapshot = _SNAPSHOT.format(dataset_id=dataset_id)
275
+ if isinstance(blocker, str) and blocker:
276
+ return _step(
277
+ "cadence",
278
+ readiness,
279
+ f"This run's table, {table_id}, has no refresh cadence recorded. "
280
+ + _NOT_READY_WHY.format(blocker=said)
281
+ + snapshot,
282
+ )
283
+ return _step(
284
+ "cadence",
285
+ f'mr-data promote {table_id} --cadence "CRON OR every Nh" --why "WHY THIS FITS THE '
286
+ 'SOURCE" --json',
287
+ f"This run's table, {table_id}, has no refresh cadence recorded. "
288
+ + _CADENCE_WHY.format(table_id=table_id)
289
+ + snapshot,
290
+ )
291
+
292
+
293
+ def _cloud_view_client(transport: Transport) -> Any:
294
+ """The Cloud table-view client on the stored device key. Tests replace it."""
295
+
296
+ # Imported here: the view module reads the credential store, which only this step needs.
297
+ from mostlyright.data_harness.thin import table_view
298
+
299
+ return table_view.cloud_client(transport)
300
+
301
+
302
+ def _view_step(
303
+ record: Mapping[str, Any], *, table_id: str, transport: Transport
304
+ ) -> tuple[dict[str, str] | None, str | None]:
305
+ """The view step when the live table has no saved view, and any note. Never raises."""
306
+
307
+ from mostlyright.data_harness.thin import table_view
308
+
309
+ try:
310
+ coordinates = table_view.live_coordinates(record)
311
+ except Exception:
312
+ coordinates = None
313
+ if coordinates is None:
314
+ return None, None
315
+ dataset_id, live = coordinates
316
+ try:
317
+ saved = table_view.saved_state(
318
+ _cloud_view_client(transport),
319
+ dataset_id=dataset_id,
320
+ table_id=table_id,
321
+ live_version_id=live,
322
+ )
323
+ except Exception as error: # a failed read must never fail the command
324
+ return None, (
325
+ f"The table view could not be checked{_refusal_said(error)}. "
326
+ f"Read mr-data table view {table_id}."
327
+ )
328
+ if saved:
329
+ return None, None
330
+ why = f"This run's table, {table_id}, has no saved view. {_VIEW_WHY}"
331
+ return _step("table_view", f"mr-data table view {table_id} --json", why), None
332
+
333
+
334
+ def _table_steps(
335
+ client: StudioV4DatasetClient,
336
+ run: Mapping[str, Any],
337
+ *,
338
+ dataset_id: str,
339
+ transport: Transport,
340
+ ) -> tuple[list[dict[str, str]], list[str]]:
341
+ """Search, view and cadence for the run's table, in that order, and any notes."""
342
+
343
+ try:
344
+ table_id = identifier(run.get("table_id"), "table identifier")
345
+ except Exception: # a record without a usable table identifier is still a delivered run
346
+ return [], ["Search, view and cadence could not be checked: the run names no table."]
347
+ steps: list[dict[str, str]] = []
348
+ notes: list[str] = []
349
+ search, note = _search_step(client, table_id)
350
+ if search is not None:
351
+ steps.append(search)
352
+ if note is not None:
353
+ notes.append(note)
354
+ record, note = _table_record(client, table_id)
355
+ if record is None:
356
+ notes.append(str(note))
357
+ return steps, notes
358
+ view, note = _view_step(record, table_id=table_id, transport=transport)
359
+ if view is not None:
360
+ steps.append(view)
361
+ if note is not None:
362
+ notes.append(note)
363
+ cadence = cadence_step(record, table_id=table_id, dataset_id=dataset_id)
364
+ if cadence is not None:
365
+ steps.append(cadence)
366
+ return steps, notes
367
+
368
+
182
369
  def _unchecked(run_id: str, note: str) -> dict[str, Any]:
183
370
  return {
184
371
  "version": DELIVERY_VERSION,
@@ -188,7 +375,13 @@ def _unchecked(run_id: str, note: str) -> dict[str, Any]:
188
375
  }
189
376
 
190
377
 
191
- def block(client: StudioV4DatasetClient, run: Mapping[str, Any], run_id: str) -> dict[str, Any]:
378
+ def block(
379
+ client: StudioV4DatasetClient,
380
+ run: Mapping[str, Any],
381
+ run_id: str,
382
+ *,
383
+ transport: Transport | None = None,
384
+ ) -> dict[str, Any]:
192
385
  """The ``delivery`` member for one succeeded full run. Never raises."""
193
386
 
194
387
  try:
@@ -208,12 +401,13 @@ def block(client: StudioV4DatasetClient, run: Mapping[str, Any], run_id: str) ->
208
401
  f"Presentation could not be checked{why}. Read mr-data dataset show {dataset_id}.",
209
402
  )
210
403
  steps = required_steps(record, dataset_id=dataset_id, run_id=run_id)
211
- search, note = _search_step(client, record, run)
212
- if search is not None:
213
- steps.insert(len(steps) - 1, search)
404
+ table, notes = _table_steps(
405
+ client, run, dataset_id=dataset_id, transport=transport or client.transport
406
+ )
407
+ steps[len(steps) - 1 : len(steps) - 1] = table
214
408
  delivered: dict[str, Any] = {"version": DELIVERY_VERSION, "scope": SCOPE, "required": steps}
215
- if note is not None:
216
- delivered["note"] = note
409
+ if notes:
410
+ delivered["note"] = " ".join(notes)
217
411
  return delivered
218
412
 
219
413
 
@@ -230,7 +424,9 @@ def for_run(
230
424
  if not applies(run, succeeded=succeeded):
231
425
  return None
232
426
  try:
233
- return block(StudioV4DatasetClient(session, transport=transport), run, run_id)
427
+ return block(
428
+ StudioV4DatasetClient(session, transport=transport), run, run_id, transport=transport
429
+ )
234
430
  except Exception: # the block must never fail the command
235
431
  return _unchecked(run_id, "Presentation could not be checked.")
236
432