mostlyright-data 0.31.8__tar.gz → 0.31.9__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.8 → mostlyright_data-0.31.9}/PKG-INFO +1 -1
  2. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/commands.md +1 -1
  3. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/promote.md +11 -0
  4. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/router.py +2 -2
  5. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4.py +9 -19
  6. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_artifacts.py +12 -3
  7. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_runs.py +147 -0
  8. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_stream.py +26 -5
  9. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_tables.py +276 -11
  10. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/render.py +10 -0
  11. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/.gitignore +0 -0
  12. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/README.md +0 -0
  13. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/pyproject.toml +0 -0
  14. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/scripts/hatch_build.py +0 -0
  15. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/SKILL.md +0 -0
  16. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/agents/openai.yaml +0 -0
  17. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +0 -0
  18. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
  19. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
  20. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
  21. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
  22. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
  23. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
  24. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
  25. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
  26. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/agent-protocol.md +0 -0
  27. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
  28. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
  29. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/boundaries.md +0 -0
  30. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
  31. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
  32. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/enriching-with-small-models.md +0 -0
  33. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/installation-parity.md +0 -0
  34. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/live-run.md +0 -0
  35. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/making-a-table-incremental.md +0 -0
  36. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/narrating-the-run.md +0 -0
  37. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
  38. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/one-install.md +0 -0
  39. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/patterns.md +0 -0
  40. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/prediction-labels.md +0 -0
  41. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/preparing-table-search.md +0 -0
  42. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/readers.md +0 -0
  43. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/receipts.md +0 -0
  44. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/recipe-test.md +0 -0
  45. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
  46. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
  47. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/reference-pages.md +0 -0
  48. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/required-protocol.md +0 -0
  49. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/source-credentials.md +0 -0
  50. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/sources.md +0 -0
  51. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
  52. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/transforms.md +0 -0
  53. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/user-communication-contract.md +0 -0
  54. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
  55. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  56. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/__init__.py +0 -0
  57. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/agent_protocol.py +0 -0
  58. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/canonical.py +0 -0
  59. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/formats.py +0 -0
  60. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  61. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/key_seam.py +0 -0
  62. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/page_coverage.py +0 -0
  63. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  64. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/session_probes.py +0 -0
  65. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/skill_assets.py +0 -0
  66. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/table_manifest.py +0 -0
  67. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  68. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  69. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  70. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/activity.py +0 -0
  71. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  72. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/backoff.py +0 -0
  73. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/categories.py +0 -0
  74. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/commands.py +0 -0
  75. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  76. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/delivery.py +0 -0
  77. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/description_shape.py +0 -0
  78. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/download.py +0 -0
  79. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/enrichment.py +0 -0
  80. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
  81. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/matching_benchmark.py +0 -0
  82. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  83. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/parity.py +0 -0
  84. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/probe.py +0 -0
  85. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
  86. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  87. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/propose.py +0 -0
  88. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/recipe.py +0 -0
  89. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  90. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/recipe_explain.py +0 -0
  91. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
  92. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
  93. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/recipe_test.py +0 -0
  94. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
  95. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/release_check.py +0 -0
  96. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/research.py +0 -0
  97. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
  98. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
  99. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/runs.py +0 -0
  100. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/session.py +0 -0
  101. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/stream.py +0 -0
  102. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  103. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/studio_capabilities.py +0 -0
  104. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/support.py +0 -0
  105. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/table_search_contract.json +0 -0
  106. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/table_view.py +0 -0
  107. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/transport.py +0 -0
  108. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  109. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  110. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
  111. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  112. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  113. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
  114. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  115. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_matching.py +0 -0
  116. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  117. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
  118. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  119. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
  120. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_search.py +0 -0
  121. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
  122. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  123. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
  124. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  125. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  126. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  127. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  128. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  129. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  130. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  131. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  132. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  133. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  134. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  135. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  136. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/login.py +0 -0
  137. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  138. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  139. {mostlyright_data-0.31.8 → mostlyright_data-0.31.9}/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.8
3
+ Version: 0.31.9
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/
@@ -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. `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. |
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. When the newest version is held from live, `table show` says `The newest version X is held: <reason>; serving Y`. `table accept-version TABLE --version VERSION_ID` releases that hold; live then derives from the newest unheld version. |
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. |
@@ -83,6 +83,17 @@ or a hold, `mr-data unpin TABLE_ID` resumes tracking, and `mr-data demote TABLE_
83
83
  schedule and withdraws the live pointer. Read the resulting table state, but do not infer a
84
84
  schedule, catch-up, or current bytes from the command name or from route acceptance.
85
85
 
86
+ **A succeeded run can be held from live.** Studio stamps every succeeded run with `verification`:
87
+ `verified`, or `degraded` with its reasons. When the new version would replace a better one, it
88
+ also stamps `live_hold`: the version is sealed and readable but not serving, and the table keeps
89
+ the version it had. `completeness` means the new version is degraded and the old one is verified.
90
+ `shrink` means it holds under half the old version's rows. `retention` means it sealed fewer rows
91
+ than the recipe's `verification.min_retained_ratio` allows. `mr-data status`, `watch` and
92
+ `receipt` say so, and `mr-data table show TABLE_ID` names the held version and the one serving.
93
+ Never report a held version as live. Find out why it is smaller or less complete first. If it is
94
+ right, `mr-data table accept-version TABLE_ID --version VERSION_ID` releases the hold. Live then
95
+ derives from the newest unheld version, so a newer version that has advanced since stays live.
96
+
86
97
  To retire several tables, do it in two commands rather than a loop: `mr-data demote TABLE_ID
87
98
  TABLE_ID ...` withdraws them, then `mr-data table archive TABLE_ID TABLE_ID ... --confirm DELETE`
88
99
  retires them. `DELETE` is the literal word and nothing else is accepted; it confirms the
@@ -164,8 +164,8 @@ HOSTED_SUMMARY = dict(
164
164
  ("reschedule", "change how often one table refreshes, without starting a run"),
165
165
  (
166
166
  "table",
167
- "report what one table is serving, explicitly resync it, rename it, or retire one or "
168
- "many",
167
+ "report what one table is serving, explicitly resync it, rename it, accept a version "
168
+ "held from live, or retire one or many",
169
169
  ),
170
170
  ("pin", "ask Studio to pin the live pointer and report the returned state"),
171
171
  ("unpin", "ask Studio to remove a pin and report the returned pointer state"),
@@ -277,9 +277,9 @@ RUN_EVENTS_PATH = "/v4/runs/{run_id}/events"
277
277
  #: bounded window of the run's log, with the sequence of the newest such event in that window. It
278
278
  #: reads the sealed record without mutating it or its ETag.
279
279
  #:
280
- #: ⚠ DECLARED WITH NO CALLER YET, on the same terms as :data:`LIST_TABLE_VERSIONS_PATH`. This
281
- #: client renders progress by following :data:`RUN_EVENTS_PATH` and folding the frames itself, so
282
- #: a command that wants one bounded snapshot rather than the stream lands against this constant.
280
+ #: ⚠ DECLARED WITH NO CALLER YET. This client renders progress by following
281
+ #: :data:`RUN_EVENTS_PATH` and folding the frames itself, so a command that wants one bounded
282
+ #: snapshot rather than the stream lands against this constant.
283
283
  RUN_PROGRESS_PATH = "/v4/runs/{run_id}/progress"
284
284
 
285
285
  #: ``GET`` -- one row per attempt of a run, oldest first and at most 32: how it ended, when it was
@@ -462,15 +462,9 @@ ARCHIVE_TABLE_PATH = "/v4/tables/{table_id}:archive"
462
462
  #: ``POST`` -- retire one whole dataset, under its typed name, and release the name it held.
463
463
  ARCHIVE_DATASET_PATH = "/v4/datasets/{dataset_id}:archive"
464
464
 
465
- #: ``GET`` -- every version ``:pin`` accepts, newest first, with the digest each one sealed.
466
- #:
467
- #: ⚠ DECLARED WITH NO CALLER YET, WHICH IS A SCHEDULE RATHER THAN A DECISION. ``pin`` takes a
468
- #: version the caller names, and the set of nameable versions is server-side: a caller never holds
469
- #: a run, so without this route it had no way to enumerate what it was allowed to name and rollback
470
- #: was reachable only by guessing. Studio's 406-03 shipped it after the whole v4 command surface
471
- #: had been written, so the command that reads it lands later against exactly this constant --
472
- #: which is why it is here and not in :data:`UNUSED_STUDIO_PATHS`: "no command reaches it yet" is
473
- #: not a reason for never calling it.
465
+ #: ``GET`` -- every version ``:pin`` accepts, newest first, with the digest each one sealed and a
466
+ #: ``live_hold`` on each version Studio held from live. ``mr-data table show`` reads its newest row
467
+ #: to say whether the newest version is held.
474
468
  LIST_TABLE_VERSIONS_PATH = "/v4/tables/{table_id}/versions"
475
469
 
476
470
  #: ``GET`` -- one table version's manifest, projected from Studio's own part mirror rather than
@@ -765,19 +759,15 @@ DECLARED_V4_PATHS: frozenset[str] = frozenset(
765
759
  # (Every line in this block narrates the promotion it sits on. What the pending set holds
766
760
  # TODAY is stated once, above `PENDING_V4_PATHS` itself, and today it holds nothing.)
767
761
  SESSION_HANDOFF_PATH,
768
- # ⚠ AND ONE ROUTE NO COMMAND READS YET. See `LIST_TABLE_VERSIONS_PATH`: it is declared
769
- # because the parity gate demands the whole public surface be accounted for, and because
770
- # the reason it has no caller today is a schedule rather than a decision never to call it.
771
762
  LIST_TABLE_VERSIONS_PATH,
772
763
  # ⚠ AND THE THREE PARTITIONED-TABLE ROUTES, promoted out of `PENDING_V4_PATHS` once the
773
764
  # parity gate proved Studio had been serving them all along. They were recorded as pending
774
765
  # under the placeholder name `{version}`; the contract spells it `{table_version_id}`, so
775
766
  # the entries asserted the absence of paths that do not exist and were true for the wrong
776
767
  # reason. `mr-data parts` calls the manifest, and `mr-data download` calls the session
777
- # minting for every part of a version its own run did not seal. The export is declared on
778
- # the same terms as `LIST_TABLE_VERSIONS_PATH` above -- the parity gate accounts for the
779
- # whole public surface, and its caller lands against exactly this constant -- not because
780
- # anything calls it yet.
768
+ # minting for every part of a version its own run did not seal. The export is declared
769
+ # because the parity gate accounts for the whole public surface, and its caller lands
770
+ # against exactly this constant -- not because anything calls it yet.
781
771
  TABLE_VERSION_MANIFEST_PATH,
782
772
  TABLE_VERSION_PARTS_SESSIONS_PATH,
783
773
  TABLE_VERSION_EXPORT_PATH,
@@ -97,6 +97,7 @@ from mostlyright.data_harness.thin.v4_runs import (
97
97
  StudioV4RunClient,
98
98
  failure_from,
99
99
  failure_sentence,
100
+ verification_members,
100
101
  )
101
102
  from mostlyright.data_harness.thin.v4_stream import check_results, terminal_event
102
103
  from mostlyright.data_harness.ux.credentials import (
@@ -857,8 +858,9 @@ def _head(
857
858
  """The three facts every answer in this module opens with.
858
859
 
859
860
  The address is the run's Dataset page when the command read the run record, and the team's
860
- Dataset listing when it did not: ``download``, ``receipt`` and ``peek`` read nothing but
861
- artifacts, so they hold no Dataset identifier to name a page with.
861
+ Dataset listing when it did not: ``download`` and ``peek`` read nothing but artifacts, so they
862
+ hold no Dataset identifier to name a page with. ``receipt`` reads the run record and passes it
863
+ when the read succeeds.
862
864
  """
863
865
 
864
866
  return {
@@ -1670,14 +1672,21 @@ def receipt(
1670
1672
  # receipt's own `artifact_id` is on the payload, so which one was read is never a guess.
1671
1673
  row = _rows_of_kind(_listing(selected, run_id), ("receipt",), run_id=run_id)[0]
1672
1674
  document = _document(selected, run_id, row)
1675
+ # The sealed receipt says how the run was built; whether Studio found its version complete,
1676
+ # and whether that version is serving, is on the run record. A refused read leaves it out.
1677
+ try:
1678
+ record: Mapping[str, Any] | None = selected.run(run_id)
1679
+ except ThinLaneError:
1680
+ record = None
1673
1681
  return {
1674
1682
  "schema_version": RECEIPT_SCHEMA,
1675
1683
  "status": "receipt_reported",
1676
- **_head(selected.session, run_id),
1684
+ **_head(selected.session, run_id, record),
1677
1685
  "artifact_id": _artifact_id(row),
1678
1686
  "digest": row.get("digest"),
1679
1687
  "size_bytes": row.get("size_bytes"),
1680
1688
  "receipt": document,
1689
+ **(verification_members(record) if record is not None else {}),
1681
1690
  }
1682
1691
 
1683
1692
 
@@ -2739,6 +2739,7 @@ def status(args: argparse.Namespace, *, client: StudioV4RunClient | None = None)
2739
2739
  )
2740
2740
  if delivered is not None:
2741
2741
  payload["delivery"] = delivered
2742
+ payload.update(verification_members(run))
2742
2743
  if diagnosing:
2743
2744
  # Imported here because the diagnosis reads this module's triple and room-fault reading,
2744
2745
  # and a module-level import in both directions is a cycle.
@@ -2860,6 +2861,144 @@ def status_exit_code(payload: Mapping[str, Any]) -> int:
2860
2861
  return 2 if payload.get("run", {}).get("status") == "failed" else 0
2861
2862
 
2862
2863
 
2864
+ # --------------------------------------------------------------------------------------------
2865
+ # Verification and the live hold
2866
+ # --------------------------------------------------------------------------------------------
2867
+
2868
+
2869
+ #: The run-record members Studio stamps on a succeeded run about whether its version is complete
2870
+ #: and whether it became live. Carried verbatim; absent on a record written before they existed.
2871
+ VERIFICATION_MEMBERS: tuple[str, ...] = ("verification", "live_hold", "live_hold_accepted")
2872
+
2873
+ #: What each ``verification.reasons`` token means, in the order Studio lists them.
2874
+ DEGRADED_REASONS: dict[str, str] = {
2875
+ "ROW_CLAMP_TRUNCATED": "a row clamp cut the table short",
2876
+ "DISCOVERY_INCOMPLETE": "a page collection's listing stopped before its end",
2877
+ "DISCOVERY_FAILED": "a page collection's listing was refused",
2878
+ "PAGES_FAILED": "pages whose last fetch failed are missing",
2879
+ "SOURCE_COUNT_SHORT": "a source delivered fewer rows or pages than it states it holds",
2880
+ }
2881
+
2882
+ #: What each ``live_hold.reason`` means for the version it is stamped on.
2883
+ LIVE_HOLD_REASONS: dict[str, str] = {
2884
+ "completeness": "it is degraded and the version it would have replaced is verified",
2885
+ "shrink": "it holds fewer than half the rows of the version it would have replaced",
2886
+ "retention": (
2887
+ "it sealed a smaller share of the rows its sources supplied than the recipe's "
2888
+ "verification.min_retained_ratio allows"
2889
+ ),
2890
+ }
2891
+
2892
+
2893
+ def accept_version_command(table_id: Any, version_id: Any) -> str:
2894
+ """The command that releases one held version."""
2895
+
2896
+ return shlex.join(
2897
+ [
2898
+ "mr-data",
2899
+ "table",
2900
+ "accept-version",
2901
+ str(table_id or "TABLE_ID"),
2902
+ "--version",
2903
+ str(version_id or "VERSION_ID"),
2904
+ ]
2905
+ )
2906
+
2907
+
2908
+ def live_hold_reason(reason: Any) -> str:
2909
+ """One hold reason in words, or the token itself when this client does not know it."""
2910
+
2911
+ if reason is None:
2912
+ return "Studio held it without naming a reason"
2913
+ return LIVE_HOLD_REASONS.get(
2914
+ str(reason), f"Studio held it for a reason this client does not know ({reason})"
2915
+ )
2916
+
2917
+
2918
+ def _tokens(value: Any) -> list[str]:
2919
+ """The string members of a list, or nothing when the member is not a list."""
2920
+
2921
+ if not isinstance(value, list):
2922
+ return []
2923
+ return [item for item in value if isinstance(item, str) and item]
2924
+
2925
+
2926
+ def verification_sentence(verification: Mapping[str, Any]) -> str:
2927
+ """``verified`` or ``degraded`` with the reasons, and any warning, as one sentence."""
2928
+
2929
+ state = verification.get("status")
2930
+ reasons = _tokens(verification.get("reasons"))
2931
+ if state == "degraded":
2932
+ said = "Degraded: the run succeeded but the version is short of what its sources hold"
2933
+ if reasons:
2934
+ said += (
2935
+ " ("
2936
+ + "; ".join(
2937
+ f"{reason}: {DEGRADED_REASONS.get(reason, reason)}" for reason in reasons
2938
+ )
2939
+ + ")"
2940
+ )
2941
+ said += "."
2942
+ elif state == "verified":
2943
+ said = "Verified: Studio found nothing missing from this version."
2944
+ else:
2945
+ said = f"Studio reports verification {state!r}."
2946
+ sealed, acquired = verification.get("sealed_rows"), verification.get("acquired_rows")
2947
+ for warning in _tokens(verification.get("warnings")):
2948
+ if warning == "SEALED_BELOW_ACQUIRED":
2949
+ said += " Warning SEALED_BELOW_ACQUIRED: the table holds under a tenth of the rows "
2950
+ said += "its sources supplied"
2951
+ if isinstance(sealed, int) and isinstance(acquired, int):
2952
+ said += f" ({sealed:,} of {acquired:,})"
2953
+ said += "."
2954
+ else:
2955
+ said += f" Warning {warning}."
2956
+ return said
2957
+
2958
+
2959
+ def live_hold_sentence(run: Mapping[str, Any]) -> str | None:
2960
+ """What a held version means for the table, and the command that releases it."""
2961
+
2962
+ hold = run.get("live_hold")
2963
+ if isinstance(hold, Mapping):
2964
+ version = run.get("table_version_id")
2965
+ named = f"Version {version}" if version else "This version"
2966
+ return (
2967
+ f"{named} is sealed but not serving: Studio held it from live because "
2968
+ f"{live_hold_reason(hold.get('reason'))}. Live kept version "
2969
+ f"{hold.get('against_version_id')}, the one it would have replaced; "
2970
+ f"mr-data table show {run.get('table_id') or 'TABLE_ID'} says what the table serves "
2971
+ f"now. If this version is right, accept it with "
2972
+ f"{accept_version_command(run.get('table_id'), version)}."
2973
+ )
2974
+ accepted = run.get("live_hold_accepted")
2975
+ if isinstance(accepted, Mapping):
2976
+ return (
2977
+ f"This version was held from live ({accepted.get('reason')}) and accepted at "
2978
+ f"{accepted.get('accepted_at')}."
2979
+ )
2980
+ return None
2981
+
2982
+
2983
+ def verification_members(run: Mapping[str, Any]) -> dict[str, Any]:
2984
+ """The verification and live-hold members a run receipt carries, or nothing.
2985
+
2986
+ The record's own members travel verbatim; the two sentences are what a person reads. A record
2987
+ written before Studio stamped them yields an empty mapping, so older runs print as before.
2988
+ """
2989
+
2990
+ members: dict[str, Any] = {
2991
+ member: run[member] for member in VERIFICATION_MEMBERS if isinstance(run.get(member), dict)
2992
+ }
2993
+ verification = members.get("verification")
2994
+ if isinstance(verification, Mapping):
2995
+ members["verification_said"] = verification_sentence(verification)
2996
+ held = live_hold_sentence(run)
2997
+ if held is not None:
2998
+ members["live_hold_said"] = held
2999
+ return members
3000
+
3001
+
2863
3002
  # --------------------------------------------------------------------------------------------
2864
3003
  # Listing them
2865
3004
  # --------------------------------------------------------------------------------------------
@@ -3176,11 +3315,13 @@ def _matches(
3176
3315
  __all__ = [
3177
3316
  "CONFIRM_REQUIRED_CODE",
3178
3317
  "DEFAULT_RUN_LIMIT",
3318
+ "DEGRADED_REASONS",
3179
3319
  "DISCARD_HISTORY_FLAG",
3180
3320
  "FAILURE_MEMBERS",
3181
3321
  "FRESH_READ_CONTROL",
3182
3322
  "GRAMMAR_NOT_SERVED_CODE",
3183
3323
  "LIST_PAGE_SIZE",
3324
+ "LIVE_HOLD_REASONS",
3184
3325
  "MAX_CLAMP_BYTES",
3185
3326
  "MAX_PAGE_HALVINGS",
3186
3327
  "MAX_RUN_PAGES",
@@ -3202,9 +3343,11 @@ __all__ = [
3202
3343
  "STEP_UP_CODES",
3203
3344
  "TERMINAL_RUN_STATUSES",
3204
3345
  "TEST_STOPPED_STATUSES",
3346
+ "VERIFICATION_MEMBERS",
3205
3347
  "WINDOW_MEMBERS",
3206
3348
  "AdaptivePage",
3207
3349
  "StudioV4RunClient",
3350
+ "accept_version_command",
3208
3351
  "approve_full",
3209
3352
  "confirm_held",
3210
3353
  "confirm_held_command",
@@ -3219,6 +3362,8 @@ __all__ = [
3219
3362
  "fresh_full_record",
3220
3363
  "is_room_fault",
3221
3364
  "listing_page_size",
3365
+ "live_hold_reason",
3366
+ "live_hold_sentence",
3222
3367
  "pages_sentence",
3223
3368
  "registered_digest",
3224
3369
  "release_full_command",
@@ -3236,4 +3381,6 @@ __all__ = [
3236
3381
  "status",
3237
3382
  "status_exit_code",
3238
3383
  "table_capabilities",
3384
+ "verification_members",
3385
+ "verification_sentence",
3239
3386
  ]
@@ -62,6 +62,7 @@ from mostlyright.data_harness.thin.v4_runs import (
62
62
  _preflight_run,
63
63
  failure_from,
64
64
  failure_sentence,
65
+ verification_members,
65
66
  )
66
67
  from mostlyright.data_harness.ux.credentials import resolve_cloud_credentials
67
68
 
@@ -246,11 +247,12 @@ def watch(
246
247
  live view is that it is live -- and the returned payload is the summary, so ``--json`` gets one
247
248
  object and a person gets a timeline followed by that object.
248
249
 
249
- ⚠ THE RUN RECORD IS READ ONCE, BEFORE THE TIMELINE, AND NOT AFTERWARDS. It is read first
250
- because it names the Dataset whose page this run is followed on. The terminal event says what
251
- happened and carries the whole failure triple, so a second GET would be asking a second
252
- authority the question the stream just answered -- and the two can disagree by exactly the
253
- width of a race.
250
+ ⚠ THE RUN RECORD IS READ BEFORE THE TIMELINE, AND AFTERWARDS ONLY FOR A SUCCESS. It is read
251
+ first because it names the Dataset whose page this run is followed on. The terminal event says
252
+ what happened and carries the whole failure triple, so a failed run is not read again: a second
253
+ GET would be asking a second authority the question the stream just answered. A succeeded run
254
+ is read once more for its ``verification`` and ``live_hold``, which the terminal event does
255
+ not carry.
254
256
 
255
257
  ⚠ THE STREAM READS THROUGH ``sessions`` AND THE ONE RECORD READ DOES NOT NEED TO. The record
256
258
  is read at once, on the token just minted; the stream is what outlives it, so the stream is
@@ -341,9 +343,28 @@ def watch(
341
343
  )
342
344
  if delivered is not None:
343
345
  payload["delivery"] = delivered
346
+ if terminal.get("event_type") == "run_succeeded":
347
+ payload.update(_succeeded_verification(current, selected, run_id))
344
348
  return payload
345
349
 
346
350
 
351
+ def _succeeded_verification(
352
+ current: StudioSession, selected: StudioV4RunClient, run_id: str
353
+ ) -> dict[str, Any]:
354
+ """The verification and live-hold members of a run that has just succeeded, or nothing.
355
+
356
+ The terminal event does not carry them: Studio stamps them on the run record in the same
357
+ write, so the record is read once more after ``run_succeeded``. A refused read leaves them
358
+ out rather than failing a watch whose run did succeed.
359
+ """
360
+
361
+ try:
362
+ record = StudioV4RunClient(current, transport=selected.transport).run(run_id)
363
+ except ThinLaneError:
364
+ return {}
365
+ return verification_members(record) if isinstance(record, Mapping) else {}
366
+
367
+
347
368
  def watch_many(
348
369
  args: argparse.Namespace,
349
370
  *,