mostlyright-data 0.25.7__tar.gz → 0.25.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 (113) hide show
  1. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/PKG-INFO +1 -1
  2. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/pyproject.toml +1 -1
  3. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/propose.py +1 -1
  4. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/recipe.py +209 -8
  5. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/router.py +24 -0
  6. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/session.py +5 -0
  7. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_artifacts.py +73 -2
  8. mostlyright_data-0.25.8/src/mostlyright/data_harness/thin/v4_connected.py +179 -0
  9. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_datasets.py +79 -5
  10. mostlyright_data-0.25.8/src/mostlyright/data_harness/thin/v4_public.py +1087 -0
  11. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_tables.py +639 -12
  12. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/vocabulary.py +16 -0
  13. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/render.py +19 -0
  14. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/.gitignore +0 -0
  15. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/README.md +0 -0
  16. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/scripts/hatch_build.py +0 -0
  17. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/SKILL.md +0 -0
  18. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/agents/openai.yaml +0 -0
  19. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +0 -0
  20. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
  21. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
  22. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
  23. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
  24. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
  25. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
  26. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
  27. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
  28. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/agent-protocol.md +0 -0
  29. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
  30. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
  31. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/boundaries.md +0 -0
  32. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
  33. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/commands.md +0 -0
  34. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
  35. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/installation-parity.md +0 -0
  36. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/live-run.md +0 -0
  37. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/narrating-the-run.md +0 -0
  38. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
  39. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/one-install.md +0 -0
  40. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/prediction-labels.md +0 -0
  41. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/promote.md +0 -0
  42. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/readers.md +0 -0
  43. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/receipts.md +0 -0
  44. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
  45. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
  46. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/reference-pages.md +0 -0
  47. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/required-protocol.md +0 -0
  48. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/source-credentials.md +0 -0
  49. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/sources.md +0 -0
  50. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
  51. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/transforms.md +0 -0
  52. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/user-communication-contract.md +0 -0
  53. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
  54. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  55. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/__init__.py +0 -0
  56. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/agent_protocol.py +0 -0
  57. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/canonical.py +0 -0
  58. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/formats.py +0 -0
  59. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  60. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/key_seam.py +0 -0
  61. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/page_coverage.py +0 -0
  62. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  63. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/session_probes.py +0 -0
  64. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/skill_assets.py +0 -0
  65. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/table_manifest.py +0 -0
  66. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  67. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  68. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  69. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/activity.py +0 -0
  70. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  71. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/categories.py +0 -0
  72. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/commands.py +0 -0
  73. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  74. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/download.py +0 -0
  75. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  76. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/parity.py +0 -0
  77. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/probe.py +0 -0
  78. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  79. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  80. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
  81. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/research.py +0 -0
  82. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/runs.py +0 -0
  83. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/stream.py +0 -0
  84. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  85. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/transport.py +0 -0
  86. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  87. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4.py +0 -0
  88. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  89. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  90. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  91. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  92. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  93. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  94. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
  95. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
  96. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  97. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
  98. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  99. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  100. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  101. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  102. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  103. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  104. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  105. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  106. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  107. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  108. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  109. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  110. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/login.py +0 -0
  111. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  112. {mostlyright_data-0.25.7 → mostlyright_data-0.25.8}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  113. {mostlyright_data-0.25.7 → mostlyright_data-0.25.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.25.7
3
+ Version: 0.25.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/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mostlyright-data"
3
- version = "0.25.7"
3
+ version = "0.25.8"
4
4
  description = "Mostly Right hosted CLI for reviewed datasets"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -2275,7 +2275,7 @@ def _dataset_conflict_remedy(refusal: ThinLaneError) -> str | None:
2275
2275
  return (
2276
2276
  "a Dataset of this name already exists in the workspace; if an earlier run of this bundle "
2277
2277
  'registered it, adopt it by id in dataset.json ({"dataset_id": "..."}) -- '
2278
- "mr-data list names the workspace's datasets -- or choose another name"
2278
+ "mr-data dataset list names the workspace's datasets -- or choose another name"
2279
2279
  )
2280
2280
 
2281
2281
 
@@ -51,17 +51,23 @@ from mostlyright.data_harness.thin.recipe_lint import lint_document, lint_refusa
51
51
  from mostlyright.data_harness.thin.session import StudioSession, open_studio_session
52
52
  from mostlyright.data_harness.thin.transport import ThinLaneError
53
53
  from mostlyright.data_harness.thin.v4 import identifier
54
- from mostlyright.data_harness.thin.v4_datasets import StudioV4DatasetClient, append_cell_body
54
+ from mostlyright.data_harness.thin.v4_datasets import (
55
+ StudioV4DatasetClient,
56
+ append_cell_body,
57
+ )
55
58
  from mostlyright.data_harness.thin.v4_narrative import v4_cell_receipt
59
+ from mostlyright.data_harness.thin.v4_runs import StudioV4RunClient
60
+ from mostlyright.data_harness.thin.v4_runs import start as start_run
56
61
  from mostlyright.data_harness.ux.credentials import resolve_cloud_credentials
57
62
  from mostlyright.data_harness.ux.plain_file import PlainFileRefusal, read_plain_file
58
63
 
59
64
  #: The one-line help both front doors print for this command.
60
- COMMAND_HELP = "register one recipe document and print the identifiers the server derived"
65
+ COMMAND_HELP = "register one recipe document, or propose a revision that also starts one replay"
61
66
 
62
67
  REGISTER_SCHEMA = f"{THIN_SCHEMA_PREFIX}-recipe.v1"
63
68
  SHOW_SCHEMA = f"{THIN_SCHEMA_PREFIX}-recipe-read.v1"
64
69
  READINESS_SCHEMA = f"{THIN_SCHEMA_PREFIX}-recipe-readiness.v1"
70
+ PROPOSE_SCHEMA = f"{THIN_SCHEMA_PREFIX}-recipe-propose.v1"
65
71
 
66
72
  #: The bound on the document. The same number the authoring lane bounded one of its six documents
67
73
  #: by -- four times the contract's own 262144-byte ceiling on a canonical recipe -- rather than a
@@ -126,6 +132,7 @@ UNREFRESHABLE_PLAN_TAIL = (
126
132
  #: `mr-data recipe show ID` is how a person says it out loud.
127
133
  SHOW = "show"
128
134
  READINESS = "readiness"
135
+ PROPOSE = "propose"
129
136
  # Studio evaluates the exact persisted admission proof for each recipe revision. Keep this
130
137
  # inventory page small enough that a normal production workspace completes each bounded request
131
138
  # inside the thin client's request deadline, while preserving the established 40,000-item
@@ -148,15 +155,17 @@ def declare_arguments(parser: argparse.ArgumentParser) -> None:
148
155
  metavar="FILE",
149
156
  help=(
150
157
  "the recipe document to register, as a path to a JSON file; the word `show`, "
151
- "followed by a recipe identifier, to read one back; or `readiness` to inventory "
152
- "whether current tables may schedule a bounded incremental refresh"
158
+ "followed by a recipe identifier, to read one back; `readiness` to inventory "
159
+ "whether current tables may schedule a bounded incremental refresh; or `propose`, "
160
+ "followed by a recipe document path and `--sources-from RUN_ID`, to register a "
161
+ "revision and start exactly one retained-input replay (MCP `propose_revision`)"
153
162
  ),
154
163
  )
155
164
  parser.add_argument(
156
165
  "recipe_id",
157
166
  nargs="?",
158
167
  metavar="RECIPE_ID",
159
- help="with `show`, the recipe to read back",
168
+ help="with `show`, the recipe to read back; with `propose`, the recipe document path",
160
169
  )
161
170
  parser.add_argument(
162
171
  "--no-lint",
@@ -171,6 +180,18 @@ def declare_arguments(parser: argparse.ArgumentParser) -> None:
171
180
  help="record that the user delegated the brief, in their own words, and register; this "
172
181
  "is how `you choose` or `just build it` is written onto the dataset as the answer it is",
173
182
  )
183
+ parser.add_argument(
184
+ "--sources-from",
185
+ dest="sources_from_run",
186
+ metavar="RUN_ID",
187
+ help="with `propose`, replay the retained raw inputs of this successful run; required, "
188
+ "and replay never becomes live",
189
+ )
190
+ parser.add_argument(
191
+ "--confirm",
192
+ action="store_true",
193
+ help="with `propose`, authorize the projected spend if the replay is held for one",
194
+ )
174
195
 
175
196
 
176
197
  #: The flags that describe a registration. ``show`` reads one back and neither can mean anything
@@ -181,6 +202,13 @@ _REGISTER_ONLY_FLAGS: tuple[tuple[str, str], ...] = (
181
202
  ("delegated", "--delegated"),
182
203
  )
183
204
 
205
+ #: Flags that belong only to ``propose``. A plain registration (or show/readiness) that carries
206
+ #: one is refused by name rather than silently dropped -- same construction as _REGISTER_ONLY_FLAGS.
207
+ _PROPOSE_ONLY_FLAGS: tuple[tuple[str, str], ...] = (
208
+ ("sources_from_run", "--sources-from"),
209
+ ("confirm", "--confirm"),
210
+ )
211
+
184
212
 
185
213
  def _session(args: argparse.Namespace) -> StudioSession:
186
214
  return open_studio_session(resolve_cloud_credentials())
@@ -1159,6 +1187,163 @@ def readiness(
1159
1187
  }
1160
1188
 
1161
1189
 
1190
+ def propose(
1191
+ args: argparse.Namespace,
1192
+ *,
1193
+ client: StudioV4DatasetClient | None = None,
1194
+ run_client: StudioV4RunClient | None = None,
1195
+ ) -> dict[str, Any]:
1196
+ """``mr-data recipe propose FILE --sources-from RUN``: register + start one replay.
1197
+
1198
+ Mirrors cloud MCP ``propose_revision``: the revision is durable even when the retained-input
1199
+ replay is held for spend confirmation or refused. Validate ``--sources-from`` before
1200
+ registration so a bad run id never leaves an orphan revision. A non-held replay refusal
1201
+ returns ``revision_registered_replay_not_started`` with the immutable coordinates; a held
1202
+ replay returns outer status ``held`` (exit 2) so a script does not treat spend-gate as success.
1203
+ """
1204
+
1205
+ document_path = getattr(args, "recipe_id", None)
1206
+ if not isinstance(document_path, str) or not document_path:
1207
+ raise ThinLaneError(
1208
+ "THIN_REQUEST_INVALID",
1209
+ "mr-data recipe propose needs the recipe document path after the word propose",
1210
+ )
1211
+ sources_raw = getattr(args, "sources_from_run", None)
1212
+ if not isinstance(sources_raw, str) or not sources_raw:
1213
+ raise ThinLaneError(
1214
+ "THIN_REQUEST_INVALID",
1215
+ "mr-data recipe propose needs --sources-from RUN_ID naming the "
1216
+ "retained inputs to replay",
1217
+ )
1218
+ # Validate before register — a bad identifier must not leave a durable revision behind.
1219
+ sources_from = identifier(sources_raw, "source run identifier")
1220
+
1221
+ register_args = argparse.Namespace(**vars(args))
1222
+ register_args.target = document_path
1223
+ register_args.recipe_id = None
1224
+ registration = register(register_args, client=client)
1225
+
1226
+ coordinates = {
1227
+ "recipe_id": registration["recipe_id"],
1228
+ "recipe_digest": registration["recipe_digest"],
1229
+ "dataset_id": registration["dataset_id"],
1230
+ "table_id": registration["table_id"],
1231
+ "source_ids": registration["source_ids"],
1232
+ }
1233
+
1234
+ run_args = argparse.Namespace(
1235
+ recipe=registration["recipe_id"],
1236
+ digest=registration["recipe_digest"],
1237
+ mode="replay",
1238
+ sources_from_run=sources_from,
1239
+ max_rows=None,
1240
+ max_source_bytes=None,
1241
+ window=None,
1242
+ resource_class=None,
1243
+ progressive=False,
1244
+ resume_capture_run_id=None,
1245
+ confirm=bool(getattr(args, "confirm", False)),
1246
+ confirm_held=None,
1247
+ cancel=None,
1248
+ retry=None,
1249
+ approve_full=None,
1250
+ )
1251
+ try:
1252
+ replay = start_run(run_args, client=run_client)
1253
+ except ThinLaneError as refusal:
1254
+ payload: dict[str, Any] = {
1255
+ "schema_version": PROPOSE_SCHEMA,
1256
+ "status": "revision_registered_replay_not_started",
1257
+ "lane": "hosted",
1258
+ **coordinates,
1259
+ "sources_from_run": sources_from,
1260
+ "refusal": {"code": refusal.code, "detail": refusal.detail},
1261
+ "idempotent_by": registration.get("idempotent_by", "recipe_digest"),
1262
+ "brief": registration["brief"],
1263
+ "next": (
1264
+ "The recipe revision was registered, but its retained-input replay did not "
1265
+ "start. Keep these immutable coordinates and correct the replay precondition "
1266
+ "before proposing again."
1267
+ ),
1268
+ }
1269
+ _copy_registration_extras(registration, payload)
1270
+ return payload
1271
+
1272
+ if replay.get("status") == "run_awaiting_confirmation":
1273
+ held: dict[str, Any] = {
1274
+ "schema_version": PROPOSE_SCHEMA,
1275
+ "status": "held",
1276
+ "lane": "hosted",
1277
+ **coordinates,
1278
+ "sources_from_run": sources_from,
1279
+ "mode": "replay",
1280
+ "idempotent_by": registration.get("idempotent_by", "recipe_digest"),
1281
+ "brief": registration["brief"],
1282
+ "replay": replay,
1283
+ "next": (
1284
+ "Show the projected cost to the user, then call "
1285
+ f"`{replay.get('confirm_command', 'mr-data run --confirm …')}` only if they "
1286
+ "agree. The replay produces repair evidence only; no full run is approved."
1287
+ ),
1288
+ }
1289
+ for key in (
1290
+ "run_id",
1291
+ "projected_bytes",
1292
+ "projected_runtime_s",
1293
+ "projected_cost",
1294
+ "confirm_command",
1295
+ "build_scope",
1296
+ ):
1297
+ if key in replay:
1298
+ held[key] = replay[key]
1299
+ if "run_id" in held:
1300
+ held["replay_run_id"] = held["run_id"]
1301
+ _copy_registration_extras(registration, held)
1302
+ return held
1303
+
1304
+ payload = {
1305
+ "schema_version": PROPOSE_SCHEMA,
1306
+ "status": "recipe_proposed",
1307
+ "lane": "hosted",
1308
+ **coordinates,
1309
+ "sources_from_run": sources_from,
1310
+ "replay": replay,
1311
+ "idempotent_by": registration.get("idempotent_by", "recipe_digest"),
1312
+ "brief": registration["brief"],
1313
+ }
1314
+ run_body = replay.get("run") if isinstance(replay.get("run"), Mapping) else None
1315
+ if isinstance(run_body, Mapping) and isinstance(run_body.get("run_id"), str):
1316
+ payload["replay_run_id"] = run_body["run_id"]
1317
+ _copy_registration_extras(registration, payload)
1318
+ return payload
1319
+
1320
+
1321
+ def _copy_registration_extras(
1322
+ registration: Mapping[str, Any], payload: dict[str, Any]
1323
+ ) -> None:
1324
+ """Carry optional register fields onto a propose receipt without inventing them."""
1325
+
1326
+ for key in (
1327
+ "warnings",
1328
+ "columns_without_description",
1329
+ "sources_without_continuation",
1330
+ "delegation_cell",
1331
+ ):
1332
+ if key in registration:
1333
+ payload[key] = registration[key]
1334
+
1335
+
1336
+ def propose_exit_code(payload: Mapping[str, Any]) -> int:
1337
+ """Held or failed replay after a durable register is a gate, not a successful propose."""
1338
+
1339
+ status = payload.get("status")
1340
+ if status in {"held", "revision_registered_replay_not_started"}:
1341
+ return 2
1342
+ return 0
1343
+
1344
+
1345
+
1346
+
1162
1347
  def recipe(
1163
1348
  args: argparse.Namespace, *, client: StudioV4DatasetClient | None = None
1164
1349
  ) -> dict[str, Any]:
@@ -1174,7 +1359,7 @@ def recipe(
1174
1359
  if args.target in {SHOW, READINESS}:
1175
1360
  given = [
1176
1361
  flag
1177
- for destination, flag in _REGISTER_ONLY_FLAGS
1362
+ for destination, flag in (*_REGISTER_ONLY_FLAGS, *_PROPOSE_ONLY_FLAGS)
1178
1363
  if getattr(args, destination, None) not in (None, False)
1179
1364
  ]
1180
1365
  if given:
@@ -1191,12 +1376,25 @@ def recipe(
1191
1376
  "mr-data recipe readiness takes no recipe identifier",
1192
1377
  )
1193
1378
  return readiness(args, client=client)
1379
+ if args.target == PROPOSE:
1380
+ return propose(args, client=client)
1381
+ propose_only = [
1382
+ flag
1383
+ for destination, flag in _PROPOSE_ONLY_FLAGS
1384
+ if getattr(args, destination, None) not in (None, False)
1385
+ ]
1386
+ if propose_only:
1387
+ raise ThinLaneError(
1388
+ "THIN_REQUEST_INVALID",
1389
+ f"{', '.join(propose_only)} belong to mr-data recipe propose, not to a plain "
1390
+ "registration; say `propose` before the document path, or drop the flag",
1391
+ )
1194
1392
  if args.recipe_id:
1195
1393
  raise ThinLaneError(
1196
1394
  "THIN_REQUEST_INVALID",
1197
1395
  f"mr-data recipe takes one document to register, `show` and a recipe identifier, "
1198
- f"or `readiness`; "
1199
- f"{args.target!r} is neither",
1396
+ f"`readiness`, or `propose` and a document path; "
1397
+ f"{args.target!r} is none of those",
1200
1398
  )
1201
1399
  return register(args, client=client)
1202
1400
 
@@ -1210,6 +1408,8 @@ __all__ = [
1210
1408
  "NAMED_UNCONTINUED_SOURCES",
1211
1409
  "NAMED_UNDESCRIBED_COLUMNS",
1212
1410
  "NDJSON_SOURCE_BYTE_CEILING",
1411
+ "PROPOSE",
1412
+ "PROPOSE_SCHEMA",
1213
1413
  "READER_SOURCE_BYTE_CEILING",
1214
1414
  "READINESS",
1215
1415
  "READINESS_PAGE_SIZE",
@@ -1226,6 +1426,7 @@ __all__ = [
1226
1426
  "columns_without_description",
1227
1427
  "continuation_warnings",
1228
1428
  "declare_arguments",
1429
+ "propose",
1229
1430
  "read_document",
1230
1431
  "readiness",
1231
1432
  "recipe",
@@ -131,6 +131,12 @@ HOSTED_SUMMARY = dict(
131
131
  ("reader", "ask Studio to normalize reader options for a family and version"),
132
132
  ("source", "queue or read one bounded source inspection"),
133
133
  ("catalog", "ask the sealed public-source catalogue what might answer a question"),
134
+ (
135
+ "public",
136
+ "read the public dataset catalogue anonymously "
137
+ "(search, show, tables, schema, sample)",
138
+ ),
139
+ ("connected", "list or connect public datasets for this workspace (device key)"),
134
140
  ("stream", "record a public wss venue and name the recording a build reads"),
135
141
  ("keys", "store a source credential by name, name the stored ones, and withdraw one"),
136
142
  ("open", "hand this terminal's session to the browser as a single-use address"),
@@ -152,6 +158,7 @@ HOSTED_SUMMARY = dict(
152
158
  ("demote", "ask Studio to withdraw the live pointer from one table or many"),
153
159
  ("watch", "stream one run's live progress, resuming across stream cuts"),
154
160
  ("download", "bring one run's artifacts back here, each one's digest checked"),
161
+ ("artifacts", "list what one run sealed, without fetching bytes"),
155
162
  ("receipt", "print the record one run sealed about how it was built"),
156
163
  ("checks", "report how each check the recipe declared came out for one run"),
157
164
  ("note", "write one cell of this run's decision record, or list what is written"),
@@ -263,6 +270,7 @@ def _hosted_parser() -> argparse.ArgumentParser:
263
270
  from mostlyright.data_harness.thin import recipe as recipe_module
264
271
  from mostlyright.data_harness.thin import (
265
272
  v4_artifacts,
273
+ v4_connected,
266
274
  v4_connections,
267
275
  v4_dataset_covers,
268
276
  v4_datasets,
@@ -299,6 +307,13 @@ def _hosted_parser() -> argparse.ArgumentParser:
299
307
 
300
308
  v4_catalog.declare_arguments(_add("catalog"))
301
309
 
310
+ from mostlyright.data_harness.thin import v4_public
311
+
312
+ v4_public.declare_arguments(_add("public"))
313
+
314
+
315
+ v4_connected.declare_arguments(_add("connected"))
316
+
302
317
  # ------------------------------------------------------------------------------------
303
318
  # The source whose bytes do not exist until somebody records them
304
319
  # ------------------------------------------------------------------------------------
@@ -404,11 +419,13 @@ def _handlers() -> dict[str, Callable[[argparse.Namespace], dict[str, Any]]]:
404
419
  stream_venue,
405
420
  v4_artifacts,
406
421
  v4_catalog,
422
+ v4_connected,
407
423
  v4_connections,
408
424
  v4_dataset_covers,
409
425
  v4_datasets,
410
426
  v4_handoff,
411
427
  v4_narrative,
428
+ v4_public,
412
429
  v4_query,
413
430
  v4_reader,
414
431
  v4_runs,
@@ -442,6 +459,8 @@ def _handlers() -> dict[str, Callable[[argparse.Namespace], dict[str, Any]]]:
442
459
  # around the command rather than around its one verb, because the verb is a
443
460
  # positional and the module is where a second one would land.
444
461
  "catalog": v4_catalog.catalog,
462
+ "public": v4_public.public,
463
+ "connected": v4_connected.connected,
445
464
  # The venue lane. One name over eight verbs, because registering a document,
446
465
  # admitting it, probing it, capturing it, subscribing to it and reading back what
447
466
  # any of those produced are steps of one act -- and eight top-level names would be
@@ -453,6 +472,7 @@ def _handlers() -> dict[str, Callable[[argparse.Namespace], dict[str, Any]]]:
453
472
  "watch": v4_stream.watch,
454
473
  "status": v4_runs.status,
455
474
  "download": v4_artifacts.download,
475
+ "artifacts": v4_artifacts.artifacts,
456
476
  "verify": v4_artifacts.verify,
457
477
  "receipt": v4_artifacts.receipt,
458
478
  "checks": v4_artifacts.checks,
@@ -488,8 +508,10 @@ def _exit_codes() -> dict[str, Callable[[dict[str, Any]], int]]:
488
508
  """
489
509
 
490
510
  from mostlyright.data_harness.thin import probe as probe_module
511
+ from mostlyright.data_harness.thin import recipe as recipe_module
491
512
  from mostlyright.data_harness.thin import (
492
513
  v4_artifacts,
514
+ v4_connected,
493
515
  v4_datasets,
494
516
  v4_query,
495
517
  v4_runs,
@@ -508,6 +530,8 @@ def _exit_codes() -> dict[str, Callable[[dict[str, Any]], int]]:
508
530
  "table": v4_tables.lifecycle_exit_code,
509
531
  "demote": v4_tables.lifecycle_exit_code,
510
532
  "dataset": v4_datasets.dataset_exit_code,
533
+ "recipe": recipe_module.propose_exit_code,
534
+ "connected": v4_connected.connected_exit_code,
511
535
  }
512
536
 
513
537
 
@@ -63,6 +63,10 @@ CLOUD_SESSION_HANDOFF_PATH = "/api/cli/session-handoffs"
63
63
  #: this client sends a mode and renders what came back rather than deciding any part of it.
64
64
  CLOUD_DATASET_ACCESS_PATH = "/api/cli/datasets/{dataset_id}/access"
65
65
 
66
+ # Workspace dataset directory for agents (`mr-data dataset list`), same facts MCP's
67
+ # `list_my_datasets` reads from Cloud's own table.
68
+ CLOUD_DATASETS_LIST_PATH = "/api/cli/datasets"
69
+
66
70
  # Projection repair after a Studio metadata write, without a publication mutation.
67
71
  CLOUD_DATASET_IDENTITY_PATH = "/api/cli/datasets/{dataset_id}/identity"
68
72
 
@@ -530,6 +534,7 @@ class CloudClient:
530
534
 
531
535
  __all__ = [
532
536
  "CLOUD_APPROVAL_DASHBOARD_PATH",
537
+ "CLOUD_DATASETS_LIST_PATH",
533
538
  "CLOUD_DATASET_ACCESS_PATH",
534
539
  "CLOUD_DATASET_DASHBOARD_PATH",
535
540
  "CLOUD_DATASET_RUN_QUERY_KEY",
@@ -83,6 +83,7 @@ from mostlyright.data_harness.thin.v4_stream import check_results, terminal_even
83
83
  from mostlyright.data_harness.ux.credentials import resolve_cloud_credentials
84
84
 
85
85
  DOWNLOAD_SCHEMA = f"{THIN_SCHEMA_PREFIX}-v4-download.v1"
86
+ ARTIFACTS_LIST_SCHEMA = f"{THIN_SCHEMA_PREFIX}-v4-artifacts-list.v1"
86
87
  RECEIPT_SCHEMA = f"{THIN_SCHEMA_PREFIX}-v4-receipt.v1"
87
88
  CHECKS_SCHEMA = f"{THIN_SCHEMA_PREFIX}-v4-checks.v1"
88
89
  PEEK_SCHEMA = f"{THIN_SCHEMA_PREFIX}-v4-peek.v1"
@@ -176,6 +177,7 @@ VERSION_KINDS: tuple[str, ...] = ("table_manifest", "table_part")
176
177
  #: The one-line help each front door prints, keyed by name so both parsers read one declaration.
177
178
  COMMAND_HELP: dict[str, str] = {
178
179
  "download": "bring one run's artifacts back to this computer, each one's digest checked",
180
+ "artifacts": "list what one run sealed, without fetching bytes (MCP run_artifacts)",
179
181
  "verify": "check one run's sealed table against its manifest, part by part with --deep",
180
182
  "receipt": "print the record one run sealed: its sources, its coverage and its raw snapshot",
181
183
  "checks": "report how each check the recipe declared came out for one run",
@@ -185,7 +187,7 @@ COMMAND_HELP: dict[str, str] = {
185
187
  #: ``peek`` and ``diff`` are NOT here: both already have a declaration in each front door and an
186
188
  #: argument surface a script may be carrying, and this plan re-points their handler rather than
187
189
  #: re-declaring them.
188
- COMMANDS: tuple[str, ...] = ("download", "receipt", "checks")
190
+ COMMANDS: tuple[str, ...] = ("download", "artifacts", "receipt", "checks")
189
191
 
190
192
  #: ``verify``, mounted on the FRONT DOOR ALONE and deliberately not through :data:`COMMANDS`.
191
193
  #:
@@ -350,6 +352,19 @@ def declare_arguments(command: str, parser: argparse.ArgumentParser) -> None:
350
352
  "many, never a folder holding some of the table."
351
353
  )
352
354
  return
355
+ if command == "artifacts":
356
+ parser.add_argument(
357
+ "action",
358
+ choices=("list",),
359
+ help="list — name every artifact this run sealed (MCP run_artifacts parity)",
360
+ )
361
+ parser.add_argument("run_id", help=_RUN_POSITIONAL)
362
+ parser.description = (
363
+ "List what one run sealed — identifiers, kinds, sizes and digests — without fetching "
364
+ "bytes. Same facts cloud MCP `run_artifacts` returns. Use `mr-data download RUN` when "
365
+ "you want the bytes themselves."
366
+ )
367
+ return
353
368
  parser.add_argument("run_id", help=_RUN_POSITIONAL)
354
369
 
355
370
 
@@ -509,7 +524,10 @@ def _document(
509
524
  def _manifest_from_bytes(raw: bytes) -> Any:
510
525
  """One sealed manifest, read through the format's own reader."""
511
526
 
512
- from mostlyright.data_harness.table_manifest import TableManifest, TableManifestError
527
+ from mostlyright.data_harness.table_manifest import (
528
+ TableManifest,
529
+ TableManifestError,
530
+ )
513
531
 
514
532
  try:
515
533
  return TableManifest.from_bytes(raw)
@@ -538,6 +556,57 @@ def _head(
538
556
  }
539
557
 
540
558
 
559
+
560
+ _ARTIFACT_LIST_MEMBERS: tuple[str, ...] = (
561
+ "artifact_id",
562
+ "kind",
563
+ "size_bytes",
564
+ "digest",
565
+ "media_type",
566
+ )
567
+
568
+
569
+ def _artifact_row(row: Mapping[str, Any]) -> dict[str, Any]:
570
+ """The five members MCP ``run_artifacts`` prints; nothing else leaves this lane."""
571
+
572
+ return {member: row[member] for member in _ARTIFACT_LIST_MEMBERS if member in row}
573
+
574
+
575
+ def artifacts(
576
+ args: argparse.Namespace, *, client: StudioV4ArtifactClient | None = None
577
+ ) -> dict[str, Any]:
578
+ """``mr-data artifacts list RUN``: what this run sealed, without fetching bytes.
579
+
580
+ MCP ``run_artifacts`` parity. Reuses the same listing client ``download`` already follows,
581
+ including cursor aggregation, so a run whose artifacts span pages is still reported whole.
582
+ """
583
+
584
+ action = getattr(args, "action", None)
585
+ if action != "list":
586
+ raise ThinLaneError(
587
+ "THIN_REQUEST_INVALID",
588
+ "mr-data artifacts takes the action `list` followed by a run identifier",
589
+ )
590
+ run_id = identifier(args.run_id, "run identifier")
591
+ selected = client or _client(args)
592
+ listing = _listing(selected, run_id)
593
+ rows = [_artifact_row(row) for row in listing]
594
+ return {
595
+ "schema_version": ARTIFACTS_LIST_SCHEMA,
596
+ "status": "artifacts_listed",
597
+ **_head(selected.session, run_id),
598
+ "count": len(rows),
599
+ "complete": True,
600
+ "artifacts": _numbered("artifact", rows),
601
+ "next": (
602
+ "this run has sealed nothing yet; ask mr-data status RUN for its state"
603
+ if not rows
604
+ else "mr-data download RUN brings the bytes back; cite each digest"
605
+ ),
606
+ }
607
+
608
+
609
+
541
610
  # --------------------------------------------------------------------------------------------
542
611
  # download
543
612
  # --------------------------------------------------------------------------------------------
@@ -1545,6 +1614,7 @@ def _summary(
1545
1614
 
1546
1615
 
1547
1616
  __all__ = [
1617
+ "ARTIFACTS_LIST_SCHEMA",
1548
1618
  "CHECKS_SCHEMA",
1549
1619
  "CHECK_SCOPE_VERSION",
1550
1620
  "COMMANDS",
@@ -1566,6 +1636,7 @@ __all__ = [
1566
1636
  "VERIFY_SCHEMA",
1567
1637
  "VERSION_KINDS",
1568
1638
  "StudioV4ArtifactClient",
1639
+ "artifacts",
1569
1640
  "checks",
1570
1641
  "checks_exit_code",
1571
1642
  "declare_arguments",