mostlyright-data 0.27.1__tar.gz → 0.28.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/PKG-INFO +1 -1
  2. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/pyproject.toml +1 -1
  3. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/SKILL.md +10 -4
  4. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +7 -0
  5. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/commands.md +1 -1
  6. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/required-protocol.md +3 -1
  7. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe.py +2 -1
  8. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/session.py +11 -0
  9. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_datasets.py +136 -4
  10. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/render.py +5 -0
  11. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/.gitignore +0 -0
  12. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/README.md +0 -0
  13. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/scripts/hatch_build.py +0 -0
  14. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/agents/openai.yaml +0 -0
  15. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
  16. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
  17. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
  18. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
  19. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
  20. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
  21. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
  22. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
  23. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/agent-protocol.md +0 -0
  24. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
  25. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
  26. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/boundaries.md +0 -0
  27. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
  28. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
  29. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/installation-parity.md +0 -0
  30. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/live-run.md +0 -0
  31. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/making-a-table-incremental.md +0 -0
  32. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/narrating-the-run.md +0 -0
  33. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
  34. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/one-install.md +0 -0
  35. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/prediction-labels.md +0 -0
  36. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/promote.md +0 -0
  37. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/readers.md +0 -0
  38. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/receipts.md +0 -0
  39. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
  40. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
  41. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/reference-pages.md +0 -0
  42. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/source-credentials.md +0 -0
  43. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/sources.md +0 -0
  44. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
  45. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/transforms.md +0 -0
  46. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/user-communication-contract.md +0 -0
  47. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
  48. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  49. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/__init__.py +0 -0
  50. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/agent_protocol.py +0 -0
  51. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/canonical.py +0 -0
  52. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/formats.py +0 -0
  53. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  54. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/key_seam.py +0 -0
  55. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/page_coverage.py +0 -0
  56. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  57. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/session_probes.py +0 -0
  58. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/skill_assets.py +0 -0
  59. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/table_manifest.py +0 -0
  60. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  61. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  62. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  63. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/activity.py +0 -0
  64. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  65. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/categories.py +0 -0
  66. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/commands.py +0 -0
  67. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  68. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/download.py +0 -0
  69. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
  70. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  71. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/parity.py +0 -0
  72. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/probe.py +0 -0
  73. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
  74. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  75. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/propose.py +0 -0
  76. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  77. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe_explain.py +0 -0
  78. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
  79. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
  80. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/research.py +0 -0
  81. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/router.py +0 -0
  82. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
  83. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/runs.py +0 -0
  84. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/stream.py +0 -0
  85. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  86. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/studio_capabilities.py +0 -0
  87. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/transport.py +0 -0
  88. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  89. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4.py +0 -0
  90. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
  91. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  92. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
  93. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  94. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  95. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  96. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  97. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
  98. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  99. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
  100. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
  101. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  102. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
  103. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
  104. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
  105. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  106. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  107. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  108. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  109. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  110. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  111. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  112. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  113. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  114. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  115. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  116. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  117. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/login.py +0 -0
  118. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  119. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  120. {mostlyright_data-0.27.1 → mostlyright_data-0.28.0}/src/mostlyright/data_harness/ux/remediation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mostlyright-data
3
- Version: 0.27.1
3
+ Version: 0.28.0
4
4
  Summary: Mostly Right hosted CLI for reviewed datasets
5
5
  Project-URL: Homepage, https://mostlyright.md/
6
6
  Project-URL: Documentation, https://mostlyright.md/docs/guides/cli/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mostlyright-data"
3
- version = "0.27.1"
3
+ version = "0.28.0"
4
4
  description = "Mostly Right hosted CLI for reviewed datasets"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -14,10 +14,16 @@ references. Load the reference for the current stage only, not the entire librar
14
14
  1. Run `mr-data whoami --json` once. If authentication needs repair, read
15
15
  [authentication](references/cloud-authentication-preflight.md) and follow the reported action.
16
16
  Never print credentials or bypass their storage protection.
17
- 2. Create the dataset once with `mr-data dataset create --name "Working title" --handoff --json`.
18
- Keep its `dataset_id`. Link the stable `dashboard_url` in the first build message; open the
19
- separate `navigation.url` in the host's visible browser if available. A missing handoff does
20
- not undo creation: do not create a duplicate dataset.
17
+ 2. If the user handed you a `dataset_id` with the request, that dataset is the one: its page is
18
+ already open in their browser. Do not create another, do not run `mr-data open`, and put that
19
+ id in the recipe's `dataset.id`. Otherwise create the dataset once with
20
+ `mr-data dataset create --name "Working title" --handoff --json` and keep its `dataset_id`.
21
+ A create that answers `open_request_reused` made nothing: it found the dataset the user already
22
+ opened for this request, so build into that `dataset_id` and do not open it again; use
23
+ `--separate` only when the user asked for another dataset. After a real create, link the stable
24
+ `dashboard_url` in the first build message and open the separate `navigation.url` in the
25
+ host's visible browser if available. A missing handoff does not undo creation: do not create a
26
+ duplicate dataset.
21
27
  3. Settle purpose, row grain, tables, coverage, required sources and update cadence before
22
28
  dependent work. In an attended build, ask unresolved material questions in one concise message
23
29
  with recommended choices; recording an assumption is not asking the user. Default to one joined,
@@ -1,5 +1,12 @@
1
1
  ### 1. Open — the page, and the link to it, in the first message
2
2
 
3
+ **If the user handed you a `dataset_id` with the request, the page already exists and is open in
4
+ their browser.** Skip this stage's create and do not open anything: hold that `dataset_id`, link its
5
+ page once in the first message, and bind the recipe to it as `dataset.id`. The same holds when
6
+ `dataset create` answers `open_request_reused`: it made nothing, its `navigation.url` is the plain
7
+ page rather than a handoff, and `navigation.open_before_research` is false. If the quoted request is
8
+ a different dataset from the one you are building, run `dataset create --separate` instead.
9
+
3
10
  Create the dataset from a working title and put it in front of whoever is watching, **before any
4
11
  source lookup**. This is the first command after the preflight — nothing reads a note, searches a
5
12
  catalog, opens a provider page or fetches a URL until it has returned:
@@ -11,7 +11,7 @@ gap rather than doing anything, and `export-hosted-candidate`, which is a backen
11
11
  | `mr-data clarify` | Check whether clarification is allowed. Use `--attended` for an active user chat and `--unattended` for a scheduled invocation; an explicit operator veto such as `MOSTLYRIGHT_ATTENDED=0` still wins. A non-TTY subprocess does not prove the user is absent. The command does not ask the person in chat or wait for a reply; the agent must do that. It exits non-zero when nobody can answer or the recipe is already registered. |
12
12
  | `mr-data probe` | `probe SOURCE_ID QUESTION_ID --dataset DATASET_ID [--kind source_inspect\|sample_rows\|profile_columns\|evaluate_expression]` asks one already-registered source one question and prints the answer. **Do not plan a source inspection around it.** Both positionals are identifiers — `QUESTION_ID` is a question identifier, which no v4 registration receipt returns — and the command rides the frozen `/v3/sessions` routes, which a deployment may have switched off. Read a source instead by registering the recipe and taking one run without `--window` under `--max-rows`, then `peek`, `query` and `receipt`. |
13
13
  | `mr-data catalog` | `catalog search "QUESTION"` asks the sealed public-source catalogue which of its entries might answer a question and ranks them best first. Every ranked entry comes back with the disposition its own facts earned — `admitted`, `human_escalation_required` or `refused` — because an entry the catalogue could not vouch for is still a finding. `--format csv` states the one data format the question requires — one token per question, never repeated — and it is not a filter: an entry that does not declare that format still comes back ranked, with disposition `refused` and `filters_match` false, so nothing is held back; `--limit N` ranks at most N, up to 25. It fetches nothing and registers nothing. The catalogue holds one provider (Data.gov) and only part of it, so it is never exhaustive and an empty answer is not evidence that no such source exists. When the answer is `catalog_unavailable`, this deployment has no catalogue to search: record the lane and carry on. |
14
- | `mr-data dataset` | Bring the dataset page into existence before there is anything on it, then fill it in while somebody watches. `dataset create --name TEXT` mints it and prints the `dataset_id`; `dataset show ID` reads it back; `dataset set ID --name TEXT --topics "a,b,c" --license ID --description-file F` writes the title, the descriptive tags, the SPDX licence and the description under the version it was read at, retrying once if somebody else wrote first, and an empty `--topics` or `--license` takes that value off the page; send `--name` and `--topics` whenever you send `--description-file`, because a description sent on its own can make Studio write a new title and new tags, and read `changed` and `written_by_studio` on the receipt for every field the write moved; a saved write whose public sync fails exits 2 and reports `public_projection_synced: false` — use `dataset sync ID` to retry that sync without rewriting Studio; `dataset note ID --heading H --blocks-file B` writes one cell of the decision record that OUTLIVES every run, and `--list` reads it back; `dataset watch ID` follows the page's own event stream; `dataset activity ID --phase P --message TEXT` says what is happening right now, silently, and is never a chat message and never a cell; `dataset publish ID [--mode public|link|private]` says who can read the dataset — `public` lists it in the public directory and serves it at an address anybody can read, `link` serves it at an unlisted address, `private` takes it back to the workspace — and `dataset publish ID --show` reads that back without changing it; `dataset archive ID --confirm-name TITLE` retires the page and frees its title, deleting nothing. |
14
+ | `mr-data dataset` | Bring the dataset page into existence before there is anything on it, then fill it in while somebody watches. `dataset create --name TEXT` mints it and prints the `dataset_id`, unless the user's own request already has an empty dataset open, in which case it answers `open_request_reused` with that `dataset_id` and mints nothing (`--separate` mints a new one for a different dataset); `dataset show ID` reads it back; `dataset set ID --name TEXT --topics "a,b,c" --license ID --description-file F` writes the title, the descriptive tags, the SPDX licence and the description under the version it was read at, retrying once if somebody else wrote first, and an empty `--topics` or `--license` takes that value off the page; send `--name` and `--topics` whenever you send `--description-file`, because a description sent on its own can make Studio write a new title and new tags, and read `changed` and `written_by_studio` on the receipt for every field the write moved; a saved write whose public sync fails exits 2 and reports `public_projection_synced: false` — use `dataset sync ID` to retry that sync without rewriting Studio; `dataset note ID --heading H --blocks-file B` writes one cell of the decision record that OUTLIVES every run, and `--list` reads it back; `dataset watch ID` follows the page's own event stream; `dataset activity ID --phase P --message TEXT` says what is happening right now, silently, and is never a chat message and never a cell; `dataset publish ID [--mode public|link|private]` says who can read the dataset — `public` lists it in the public directory and serves it at an address anybody can read, `link` serves it at an unlisted address, `private` takes it back to the workspace — and `dataset publish ID --show` reads that back without changing it; `dataset archive ID --confirm-name TITLE` retires the page and frees its title, deleting nothing. |
15
15
  | `mr-data recipe` | Register one recipe document — dataset, question, table plan, sources, transform, checks and units — in one call, and print the identifiers the server derived. The document is read first and every fault comes back in one refusal, each with the JSON pointer that names it; `--no-lint` sends it as written instead. Registration refuses `THIN_BRIEF_MISSING` when the dataset's record carries no answered question and no delegation, and `THIN_RECIPE_DATASET_UNBOUND` when the document names no `dataset.id`; `--delegated "their words"` writes the delegation onto the dataset and registers in the same command, and clears the first of those two and never the second. `mr-data recipe show ID` reads one back. `mr-data recipe explain FILE` (or a recipe identifier) lists every reason its table can or cannot refresh, each with a rule code and Studio's suggested repair, and exits 2 when one blocks it. `mr-data recipe forecast RECIPE_ID --digest DIGEST` says what one run would ask of each source before it runs, and exits 3 when the run would overrun today's platform quota or one attempt. `mr-data recipe readiness --table TABLE_ID --current-only` reads the revision one table runs now. [Making a table incremental](making-a-table-incremental.md#the-loop-explain-forecast-run-diagnose) says when to use each. |
16
16
  | `mr-data cover` | Generate and attach one branded 1200×630 dataset cover. Give it the dataset ID and what the image should depict; Studio fixes the model, single-color style, curated random palette, dimensions and storage. |
17
17
  | `mr-data run` | Start one run against a registered recipe, named as `--recipe RECIPE_ID --digest RECIPE_DIGEST` — both required, neither positional, and the digest is the bare hex the registration receipt printed. The mode is one of six: `sample`, `full`, `refresh`, `backfill`, `compact` and `replay`. Four have a shorthand flag (`--sample`, `--full`, `--refresh`, `--backfill`) and two do not, so write `--mode MODE`, which is accepted for every one of them. A refresh executes only Studio's persisted source-action plan, and a plan shape it does not refresh returns `RESYNC_REQUIRED` before acquisition; [which plans refresh](making-a-table-incremental.md#which-plans-refresh) lists them, and `mr-data recipe explain` says which one a table is. Use `mr-data table resync TABLE_ID --request-id UUID` for an explicit full source reread, retaining that UUID if the response is uncertain. `--backfill` states the exact window with `--window START END`. `--mode replay --sources-from RUN_ID` asks Studio to run the registered revision against the RETAINED RAW INPUTS of one named successful run of the same table: nothing is fetched from this computer, nothing is acquired again, the result is compared against that run and never becomes the live version, and it neither asks for nor records an approval. Where Studio has replay switched off, or the named run is not successful, not the same table, or no longer retains its inputs, it answers a typed refusal — report the code rather than retrying in another mode. `--sources-from` on any other mode is refused `THIN_ARGUMENT_INVALID` before anything is sent. `--max-rows` and `--max-source-bytes` bound ONE SOURCE rather than the finished table. A large run is held for a spend confirmation, which `--confirm` settles and whose printed `confirm_command` re-states every argument the request carried. `--approve-full RUN_ID` releases the full an existing legacy sample-first pair is holding once its preview has succeeded, naming either half of the pair; where the deployment still wants a person at a browser it answers `THIN_INTERACTIVE_HUMAN_REQUIRED` and names the run page. `--retry RUN_ID` tries one failed run again when its triple carries `room_fault: true`, re-stating that run's own coordinate under a byte ceiling that cannot narrow it, and refuses with the triple when the recipe was at fault. `--cancel RUN` stops one. |
@@ -21,7 +21,9 @@ are the first two acts of the session after the preflight — before the durable
21
21
  before `mr-data runs`, before a catalog is searched, before a provider's page is opened and before
22
22
  any URL is fetched. Research is what fills the page, and a page created after the research already
23
23
  happened is the assertion this stage exists to prevent. Nothing in stage 1 depends on knowing the
24
- sources: the title is a working one and stage 5 settles it.
24
+ sources: the title is a working one and stage 5 settles it. A `dataset_id` handed over with the
25
+ request, or a create that answers `open_request_reused`, is that page already open: build into it
26
+ and open nothing.
25
27
 
26
28
  **Then establish whether anybody can be asked.** When the host provides an active user
27
29
  conversation, pass `--attended`; a tool subprocess without a terminal is not proof that the user
@@ -1231,7 +1231,8 @@ def register(
1231
1231
  DATASET_UNBOUND_CODE,
1232
1232
  "this document names no dataset.id, so there is no page whose record could carry the "
1233
1233
  "brief this recipe rests on, and no page for the build to appear on. State the "
1234
- "identifier mr-data dataset create printed. --delegated cannot help here: it records "
1234
+ "dataset identifier you were handed with the request, or the one mr-data dataset "
1235
+ "create printed. --delegated cannot help here: it records "
1235
1236
  "a delegation onto a dataset, and this document names none",
1236
1237
  )
1237
1238
  delegated = getattr(args, "delegated", None)
@@ -111,6 +111,17 @@ CLOUD_DATASET_RESOLVE_PATH = "/api/cli/datasets/resolve"
111
111
  #: What the resolve route stamps on both of its answers.
112
112
  CLOUD_DATASET_RESOLVED_SCHEMA = "mostlyright-cli-dataset-resolved.v1"
113
113
 
114
+ #: Cloud's device-key route that names the dataset a person already created for the request they
115
+ #: handed their AI tool, while that dataset is still empty and waiting for the build.
116
+ #:
117
+ #: ⚠ ASKED BEFORE A DATASET IS MINTED, BECAUSE A SECOND ONE IS THE BUG (owner, 2026-09-29: "ONE
118
+ #: DATASET, one URL, one experience in one window"). ``/datasets/new`` creates the dataset and opens
119
+ #: its page before the handoff is pasted, and an agent that ran ``dataset create`` anyway built onto
120
+ #: a second page with its own address. Only Cloud holds the request, so only Cloud can answer. It
121
+ #: answers 200 with ``open_request: null`` when nothing is waiting, so a 404 means a Cloud older
122
+ #: than the route and the client carries on as it did before.
123
+ CLOUD_OPEN_REQUEST_PATH = "/api/cli/datasets/open-request"
124
+
114
125
  MAX_TOKEN_RESPONSE_BYTES = 32 * 1024
115
126
  TOKEN_EXCHANGE_RETRY_ATTEMPTS = 2
116
127
  TOKEN_EXCHANGE_MAX_RETRY_SECONDS = 30.0
@@ -94,6 +94,7 @@ module moved.
94
94
  from __future__ import annotations
95
95
 
96
96
  import argparse
97
+ import http.client
97
98
  import re
98
99
  import time
99
100
  from collections.abc import Callable, Collection, Mapping, Sequence
@@ -129,6 +130,7 @@ from mostlyright.data_harness.thin.session import (
129
130
  CLOUD_DATASET_RESOLVE_PATH,
130
131
  CLOUD_DATASET_RESOLVED_SCHEMA,
131
132
  CLOUD_DATASETS_LIST_PATH,
133
+ CLOUD_OPEN_REQUEST_PATH,
132
134
  CLOUD_REFUSAL_CODE,
133
135
  MAX_JSON_RESPONSE_BYTES,
134
136
  MAX_STATED_MESSAGE_CHARS,
@@ -143,7 +145,7 @@ from mostlyright.data_harness.thin.session import (
143
145
  stated_sentence,
144
146
  )
145
147
  from mostlyright.data_harness.thin.stream import RunEventStream
146
- from mostlyright.data_harness.thin.transport import ThinLaneError, Transport
148
+ from mostlyright.data_harness.thin.transport import ThinLaneError, Transport, UrlLibTransport
147
149
  from mostlyright.data_harness.thin.v4 import (
148
150
  ARCHIVE_DATASET_PATH,
149
151
  COMMAND_REQUEST_VERSION_FLOOR,
@@ -173,7 +175,11 @@ from mostlyright.data_harness.thin.v4_public import (
173
175
  is_public_slug,
174
176
  show_public_dataset,
175
177
  )
176
- from mostlyright.data_harness.ux.credentials import LoginError, resolve_cloud_credentials
178
+ from mostlyright.data_harness.ux.credentials import (
179
+ LoginError,
180
+ ResolvedCloudCredentials,
181
+ resolve_cloud_credentials,
182
+ )
177
183
  from mostlyright.data_harness.ux.plain_file import PlainFileRefusal, read_plain_file
178
184
 
179
185
  CREATE_SCHEMA = f"{THIN_SCHEMA_PREFIX}-v4-dataset-created.v1"
@@ -408,6 +414,9 @@ _NO_SUCH_DATASET = (
408
414
  CLOUD_COMPOSED_REFUSAL = "step_up_required"
409
415
 
410
416
 
417
+ _OPEN_REQUEST_ACT = "ask whether your request already has a dataset"
418
+
419
+
411
420
  class CloudDatasetAccessClient(CloudClient):
412
421
  """Dataset access and identity routes over the shared device-key Cloud client.
413
422
 
@@ -444,6 +453,11 @@ class CloudDatasetAccessClient(CloudClient):
444
453
  maximum=MAX_JSON_RESPONSE_BYTES,
445
454
  )
446
455
 
456
+ def open_request(self) -> dict[str, Any]:
457
+ """Ask which dataset this person already created for their request, if one is waiting."""
458
+
459
+ return self.call("GET", CLOUD_OPEN_REQUEST_PATH, act=_OPEN_REQUEST_ACT)
460
+
447
461
  def resolve_dataset(self, reference: str) -> dict[str, Any]:
448
462
  """Ask Cloud which dataset a slug, a dataset URL or either uuid names, reading nothing else.
449
463
 
@@ -701,6 +715,14 @@ def declare_arguments(parser: argparse.ArgumentParser) -> None:
701
715
  action="store_true",
702
716
  help="With create, also prepare the address the host opens immediately",
703
717
  )
718
+ parser.add_argument(
719
+ "--separate",
720
+ action="store_true",
721
+ help=(
722
+ "With create, make a new dataset even while the page the user opened for their "
723
+ "request is still empty. Only when the user asked for another, separate dataset"
724
+ ),
725
+ )
704
726
  parser.add_argument(
705
727
  "--description-file",
706
728
  dest="description_file",
@@ -1092,17 +1114,125 @@ def _replayed(headers: Mapping[str, str]) -> bool:
1092
1114
  # ------------------------------------------------------------------------------------------------
1093
1115
 
1094
1116
 
1117
+ #: How long ``dataset create`` waits for Cloud's answer about an open request. The question is
1118
+ #: advisory, so it gets the short clock the activity report gets rather than a command's thirty
1119
+ #: seconds: a slow Cloud may not add half a minute to every create.
1120
+ OPEN_REQUEST_TIMEOUT_SECONDS = 5.0
1121
+
1122
+
1123
+ def open_request(
1124
+ cloud: CloudDatasetAccessClient | None = None,
1125
+ *,
1126
+ credentials: ResolvedCloudCredentials | None = None,
1127
+ ) -> dict[str, Any] | None:
1128
+ """The dataset the person already created for their request, while it waits for the build.
1129
+
1130
+ ⚠ NEVER THE REASON A DATASET CANNOT BE MADE. No credential, a Cloud older than the route (its
1131
+ 404), a refusal, an outage, a timeout, a reset connection or an answer this client cannot read
1132
+ all mean "nothing to reuse", and ``dataset create`` does what it did before the route existed.
1133
+ The rule deciding what is waiting is Cloud's (``lib/mcp/handoff-adoption.ts`` there); a second
1134
+ opinion here would drift.
1135
+ """
1136
+
1137
+ try:
1138
+ selected = cloud or CloudDatasetAccessClient(
1139
+ credentials or resolve_cloud_credentials(),
1140
+ transport=UrlLibTransport(timeout=OPEN_REQUEST_TIMEOUT_SECONDS),
1141
+ )
1142
+ answer = selected.open_request()
1143
+ except (ThinLaneError, LoginError, OSError, http.client.HTTPException, ValueError):
1144
+ return None
1145
+ waiting = answer.get("open_request") if isinstance(answer, Mapping) else None
1146
+ if not isinstance(waiting, Mapping):
1147
+ return None
1148
+ try:
1149
+ dataset_id = str(UUID(str(waiting.get("dataset_id"))))
1150
+ except ValueError:
1151
+ return None
1152
+ if dataset_id != waiting.get("dataset_id"):
1153
+ return None
1154
+ try:
1155
+ dashboard_url = dataset_dashboard_url(selected.cloud_url, dataset_id)
1156
+ except ThinLaneError:
1157
+ return None
1158
+ request = waiting.get("request")
1159
+ name = waiting.get("name")
1160
+ return {
1161
+ "dataset_id": dataset_id,
1162
+ "name": name if isinstance(name, str) else None,
1163
+ "request": request if isinstance(request, str) else "",
1164
+ "dashboard_url": dashboard_url,
1165
+ }
1166
+
1167
+
1168
+ def _reused_open_request(waiting: Mapping[str, Any]) -> dict[str, Any]:
1169
+ """The receipt for a create that found the person's own request waiting, and made nothing."""
1170
+
1171
+ dataset_id = waiting["dataset_id"]
1172
+ asked = f' ("{waiting["request"]}")' if waiting["request"] else ""
1173
+ return {
1174
+ "schema_version": CREATE_SCHEMA,
1175
+ "navigation": {
1176
+ "url": waiting["dashboard_url"],
1177
+ "target": f"/datasets/{dataset_id}",
1178
+ # Already open in the person's browser: opening it again is the second window.
1179
+ "open_before_research": False,
1180
+ "follow_agent": True,
1181
+ "status": "already_open",
1182
+ },
1183
+ "status": "open_request_reused",
1184
+ "lane": "hosted",
1185
+ "reused_open_request": True,
1186
+ "dataset_id": dataset_id,
1187
+ "name": waiting["name"],
1188
+ "version": None,
1189
+ "dashboard_url": waiting["dashboard_url"],
1190
+ "note": (
1191
+ f"No dataset was created. The user already created this dataset for their "
1192
+ f"request{asked} and its page is open in their browser, waiting for this build. If "
1193
+ f"that request is the one you are building, put this dataset_id in the recipe's "
1194
+ f"dataset.id and build into it; do not run mr-data open, and do not show the user "
1195
+ f"another dataset link. If your task is a different dataset from that request, run "
1196
+ f"mr-data dataset create --separate instead."
1197
+ ),
1198
+ "next": (
1199
+ f"The --name and description you gave were not applied. Give this dataset its title "
1200
+ f"and description with mr-data dataset set {dataset_id} --name ... --topics ... "
1201
+ f"--description-file ..."
1202
+ ),
1203
+ }
1204
+
1205
+
1095
1206
  def create(
1096
1207
  args: argparse.Namespace,
1097
1208
  *,
1098
1209
  client: StudioV4DatasetClient | None = None,
1099
1210
  stdin: Any = None,
1211
+ cloud: CloudDatasetAccessClient | None = None,
1100
1212
  ) -> dict[str, Any]:
1101
- """``mr-data dataset create --name TEXT``: the page, before anything is on it."""
1213
+ """``mr-data dataset create --name TEXT``: the page, before anything is on it.
1214
+
1215
+ ⚠ UNLESS THE PERSON'S OWN REQUEST ALREADY HAS ONE (owner, 2026-09-29). A dataset created on
1216
+ ``/datasets/new`` is open in their browser before the agent starts; creating another put the
1217
+ build on a second page with its own address. Cloud is asked first (:func:`open_request`), and
1218
+ a waiting dataset is answered instead of a new one. ``--separate`` skips the question. An
1219
+ injected Studio client asks only an injected Cloud: a caller that substituted one origin has
1220
+ not agreed to reach the other for real.
1221
+ """
1102
1222
 
1103
1223
  name = checked_name(getattr(args, "name", None), needed_by="create")
1104
1224
  description = read_description(args, stdin=stdin)
1105
- selected = client or _client(args)
1225
+ # Resolved once and shared, so a keychain is read (and may prompt) once per create.
1226
+ credentials = resolve_cloud_credentials() if client is None else None
1227
+ if not getattr(args, "separate", False) and (client is None or cloud is not None):
1228
+ waiting = open_request(cloud, credentials=credentials)
1229
+ if waiting is not None:
1230
+ return _reused_open_request(waiting)
1231
+ if client is not None:
1232
+ selected = client
1233
+ else:
1234
+ assert credentials is not None
1235
+ selected = StudioV4DatasetClient(open_studio_session(credentials))
1106
1236
  record = selected.create_dataset(name=name, description=description)
1107
1237
  dataset_id = _dataset_id(record, verb="create")
1108
1238
  navigation = {
@@ -2355,6 +2485,8 @@ def dataset(
2355
2485
  action = getattr(args, "action", None)
2356
2486
  if getattr(args, "handoff", False) and action != "create":
2357
2487
  raise ThinLaneError("THIN_REQUEST_INVALID", "--handoff belongs to dataset create")
2488
+ if getattr(args, "separate", False) and action != "create":
2489
+ raise ThinLaneError("THIN_REQUEST_INVALID", "--separate belongs to dataset create")
2358
2490
  if getattr(args, "expected_version", None) is not None and action not in {"set", "draft"}:
2359
2491
  raise ThinLaneError("THIN_REQUEST_INVALID", "--expected-version belongs to set or draft")
2360
2492
  if action == "draft" and getattr(args, "name", None) is not None:
@@ -346,6 +346,11 @@ STATUS_HEADLINES: dict[str, str] = {
346
346
  # two: "the page is there now" and "the page has been corrected" are not one event, and a
347
347
  # single word covering them would let a rename read as a page that was just created.
348
348
  "dataset_created": "The dataset page exists now, and it is empty. Open it before you start.",
349
+ # ⚠ NOT "CREATED", AND NOT "OPEN IT". Nothing was made: the person's own request already had a
350
+ # page, open in their browser, and a second window onto it is the bug this answer exists for.
351
+ "open_request_reused": (
352
+ "No dataset was created. Build into the one the user already opened for this request."
353
+ ),
349
354
  "dashboard_ready": "The dataset page is ready to open.",
350
355
  "description_drafting": "The description draft is saved for preview.",
351
356
  "dataset_reported": "Here is the dataset page as Studio holds it.",