mostlyright-data 0.21.2__tar.gz → 0.22.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 (74) hide show
  1. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/PKG-INFO +6 -1
  2. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/README.md +5 -0
  3. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/pyproject.toml +1 -1
  4. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/skills/mr-data-build/SKILL.md +21 -3
  5. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4.py +1 -1
  6. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_runs.py +27 -2
  7. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/render.py +1 -0
  8. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/.gitignore +0 -0
  9. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/scripts/hatch_build.py +0 -0
  10. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/skills/mr-data-build/agents/openai.yaml +0 -0
  11. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  12. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/__init__.py +0 -0
  13. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/canonical.py +0 -0
  14. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/formats.py +0 -0
  15. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  16. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/key_seam.py +0 -0
  17. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/page_coverage.py +0 -0
  18. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  19. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/session_probes.py +0 -0
  20. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/skill_assets.py +0 -0
  21. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/table_manifest.py +0 -0
  22. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  23. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  24. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  25. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/activity.py +0 -0
  26. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  27. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/categories.py +0 -0
  28. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/commands.py +0 -0
  29. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  30. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/download.py +0 -0
  31. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  32. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/parity.py +0 -0
  33. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/probe.py +0 -0
  34. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  35. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/propose.py +0 -0
  36. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/recipe.py +0 -0
  37. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  38. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
  39. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/research.py +0 -0
  40. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/router.py +0 -0
  41. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/runs.py +0 -0
  42. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/session.py +0 -0
  43. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/stream.py +0 -0
  44. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  45. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/transport.py +0 -0
  46. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  47. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
  48. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  49. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  50. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  51. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
  52. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  53. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  54. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  55. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  56. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
  57. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
  58. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
  59. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  60. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  61. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  62. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  63. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  64. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  65. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  66. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  67. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  68. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  69. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  70. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  71. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/login.py +0 -0
  72. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  73. {mostlyright_data-0.21.2 → mostlyright_data-0.22.0}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  74. {mostlyright_data-0.21.2 → mostlyright_data-0.22.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.21.2
3
+ Version: 0.22.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/
@@ -82,6 +82,11 @@ mr-data checks RUN_ID
82
82
  mr-data download RUN_ID --output ./out
83
83
  ```
84
84
 
85
+ Run submission is idempotent: sending identical recipe, mode and bounds returns the same run,
86
+ including a previous failure. The CLI reports a non-queued result as `run_returned` and exits
87
+ with code 2 when that run failed. Read its failure before retrying; `mr-data run --retry RUN_ID`
88
+ can reissue supported platform execution failures without changing the recipe.
89
+
85
90
  Use the identifiers returned by registration and run submission. `mr-data dataset create`
86
91
  creates a dataset page before a recipe is ready. `mr-data watch RUN_ID` follows a submitted run.
87
92
 
@@ -70,6 +70,11 @@ mr-data checks RUN_ID
70
70
  mr-data download RUN_ID --output ./out
71
71
  ```
72
72
 
73
+ Run submission is idempotent: sending identical recipe, mode and bounds returns the same run,
74
+ including a previous failure. The CLI reports a non-queued result as `run_returned` and exits
75
+ with code 2 when that run failed. Read its failure before retrying; `mr-data run --retry RUN_ID`
76
+ can reissue supported platform execution failures without changing the recipe.
77
+
73
78
  Use the identifiers returned by registration and run submission. `mr-data dataset create`
74
79
  creates a dataset page before a recipe is ready. `mr-data watch RUN_ID` follows a submitted run.
75
80
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mostlyright-data"
3
- version = "0.21.2"
3
+ version = "0.22.0"
4
4
  description = "Mostly Right hosted CLI for reviewed datasets"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1867,8 +1867,10 @@ Write it as a source whose connector is that coordinate plus a `collection` memb
1867
1867
 
1868
1868
  - `collection.discovery` says how the corpus is LISTED — `json_api` with RFC 6901 pointers,
1869
1869
  `html_index` with the reader's closed selectors, `sitemap`, or an `explicit` member list — and
1870
- which pagination grammar the listing uses. The run follows only that grammar and never a link
1871
- found inside a detail page.
1870
+ which pagination grammar the listing uses. `source_rows` instead names an upstream source and
1871
+ its `url_column`, optionally `base_url_column`: acquisition runs upstream first, resolves and
1872
+ deduplicates links, and preserves incomplete upstream coverage. Use only declared dependencies;
1873
+ this is not arbitrary recursive crawling. It requires the matching Studio API/worker release.
1872
1874
  - `collection.pages` says what may be fetched and how much: `allowed_origins` (which must include
1873
1875
  the discovery address's own origin), `max_pages`, `max_fetches_per_run`, `concurrency` (1 or 2),
1874
1876
  `min_interval_seconds` as a whole number of seconds (one or more), `request_timeout_seconds`,
@@ -1877,7 +1879,8 @@ Write it as a source whose connector is that coordinate plus a `collection` memb
1877
1879
  - `limits.max_requests` is REQUIRED and bounds every request the run makes, discovery and detail
1878
1880
  pages together. `limits.max_source_bytes` bounds the fetched bytes; `limits.max_rows` clamps the
1879
1881
  merged relation.
1880
- - The Reader pin is required, must be `html.web_extract`, and is the SAME settings for every page.
1882
+ - The Reader pin is required, must be `html.web_extract`, `html.tabular`, `json.tabular`, or
1883
+ `xml.tabular`, and uses the same settings for every page.
1881
1884
 
1882
1885
  **`limits.max_requests` is charged per HTTP request, redirect hops and retries included** — a page
1883
1886
  that redirects once costs two. Registration only checks that
@@ -1888,6 +1891,10 @@ redirects are likely — trailing slashes, `http`→`https`, `www`, which is mos
1888
1891
  `limits.max_requests >= discovery.max_requests + 2 * max_fetches_per_run`. A budget larger than the
1889
1892
  corpus can absorb costs nothing.
1890
1893
 
1894
+ For a dependent graph, declare recipe-level `acquisition_limits` with `max_requests` and
1895
+ `max_source_bytes`. Every source must explicitly state both ceilings; their sums must fit the
1896
+ totals. Unused capacity does not transfer between sources.
1897
+
1891
1898
  The relation the transform reads is the reader's columns plus `page_id`, `page_url`,
1892
1899
  `page_fetched_at`, `page_content_sha256`, `page_revision`, `page_discovered_at` and `page_ordinal`.
1893
1900
  Those names are reserved: a reader that declares one is a registration refusal. Select them in the
@@ -2011,6 +2018,8 @@ decoder. Zero matches and multiple matches both refuse.
2011
2018
  | `archive.gzip@1.1.0` | Same; also accepts a response served as `application/octet-stream`. |
2012
2019
  | `archive.tar@1.0.0` | Same as `archive.zip@1.0.0`. |
2013
2020
  | `archive.tar@1.1.0` | Same; also accepts a response served as `application/octet-stream`. |
2021
+ | `archive.tar@2.0.0` | Explicit `compression: none or gzip`, `encoding: utf-8 or cp1252`, `member` and `member_format: csv, tsv, semicolon or lines`. Lines also accepts exact `members` list, emitting member_name, line_number, raw_line. |
2022
+ | `text.lines@1.0.0` | Explicit `encoding: utf-8 or cp1252`; preserves physical lines as line_number, raw_line without delimiter or quote inference. |
2014
2023
  | `spreadsheet.xlsx@1.0.0` | `sheet`, `header_row`, and `range`. The heading row must be the first row of the range. |
2015
2024
  | `spreadsheet.xlsx@1.1.0` | Same; also accepts a response served as `application/octet-stream`. |
2016
2025
  | `delimited_text@1.0.0` | `delimiter` (comma or tab); optional `header: true`; optional `encoding: utf-8`. |
@@ -2022,6 +2031,7 @@ decoder. Zero matches and multiple matches both refuse.
2022
2031
  | `html.tabular@1.0.0` | Absolute record element path and relative text/attribute field selectors; fields are required unless `required: false`. |
2023
2032
  | `html.web_extract@1.0.0` | `mode: dom` with closed CSS-like record/field selectors, or `mode: json` with an inert JSON script selector and RFC 6901 field pointers. |
2024
2033
  | `html.web_extract@1.1.0` | Same settings as 1.0.0; extracted text follows DOM order, with one space at block-element boundaries and `<br>` and none between inline runs. |
2034
+ | `html.web_extract@1.2.0` | Same text as 1.1.0; bounded integer `:nth-child(N)` / `:nth-of-type(N)` and field `scope: ancestor` with `ancestor_selector`. Requires the corresponding deployed certification. |
2025
2035
  | `protobuf.tabular@1.0.0` | Digest-pinned base64 FileDescriptorSet, fully-qualified message type, `single` or `length_delimited` framing, and fields with an explicit repeated `explode` or `refuse` choice. |
2026
2036
  | `parquet.tabular@1.0.0` | Optional `columns` selects a bounded ordered Parquet projection. |
2027
2037
  | `xbrl.facts@1.0.0` | `document_kind` (`xbrl` or constrained XML `ixbrl`) and exact namespace/name fact projection; contexts, units, and metadata remain evidence fields, not joins. |
@@ -2241,3 +2251,11 @@ The floating pill represents only your active work. Start `dataset activity` imm
2241
2251
  When recovering, report what you are trying now; an earlier failed attempt belongs in the record. Before every final handoff, cancellation, or exhausted stop, report `--phase done` with a truthful final sentence (for example, “Dataset ready to explore” or “Stopped before the build completed”). Do this even when tables are not enabled. Only use `waiting_on_you` when a question is actually open — the brief at stage 2, the plan at stage 4, or the full build after a preview — and never under a delegation, which leaves nothing to wait on. A table going live does not finish your agent session.
2242
2252
 
2243
2253
  Stage 1 opened the dataset before research began; keep that same tab. Enable its optional Follow agent control for a watch-along experience; manual scrolling pauses it and must never be overridden.
2254
+
2255
+ ## Recovering an import failure
2256
+
2257
+ For a deterministic reader or transform failure, use the reported source, line and reason before changing settings. Do not repeatedly submit the same failed recipe or guess an encoding. Repeated run submission may return the existing run with `status: run_returned`; inspect `run.status` and its failure rather than treating it as new work. Use `--retry` only when the CLI identifies the failure as retryable.
2258
+
2259
+ For malformed delimited text, use the hosted `text.lines@1.0.0` reader with explicit encoding to retain physical lines, then inspect field counts and metadata before authoring the final parse. For compressed TAR files, use `archive.tar@2.0.0` with an explicit wrapper and exact member names. These readers preserve evidence; they do not repair values or prove a guessed encoding. Reader implementations are backend modules and are not importable from the thin CLI installation.
2260
+
2261
+ Estimate the final output separately from each source. Ordinary builds retain a 10-million-row output ceiling; increasing per-source limits does not increase it. A manifest over parts does not by itself make arbitrary SQL builds incremental. Use a supported partitioned execution capability only when the deployed worker admits it.
@@ -105,7 +105,7 @@ from mostlyright.data_harness.thin.transport import ThinLaneError
105
105
  #: ``JOB_INVALID`` outright, which in production was every refresh a collection epoch was offered
106
106
  #: for rather than only the collection runs. This package must not reach a Studio older than the
107
107
  #: commit it pins; ``docs/V4-WORKER-PROTOCOL.md`` states it beside the layout's own ordering rule.
108
- PINNED_V4_OPENAPI_SOURCE_SHA256 = "2a83dda60e84c166f95ea18af747985bfeeb5887cb48d2c72d4335e83720dc37"
108
+ PINNED_V4_OPENAPI_SOURCE_SHA256 = "2b854c3c4fae7c30a6219ee99ea814f80c57fbf0251ed22a0b66b002b598e6f7"
109
109
  PINNED_V4_CONTRACT_VERSION = "4.7.0"
110
110
 
111
111
  # --------------------------------------------------------------------------------------------
@@ -1242,7 +1242,27 @@ def start(args: argparse.Namespace, *, client: StudioV4RunClient | None = None)
1242
1242
  body = create_run_body(args)
1243
1243
  selected = client or _client(args)
1244
1244
  try:
1245
- return _started(selected.create_run(body), selected.session, status="run_queued")
1245
+ run = selected.create_run(body)
1246
+ # The create route is idempotent and can return a previous terminal result.
1247
+ # Report its actual state rather than promising that another worker was queued.
1248
+ state = run.get("status")
1249
+ payload = _started(
1250
+ run, selected.session, status="run_queued" if state == "queued" else "run_returned"
1251
+ )
1252
+ if state != "queued":
1253
+ payload["message"] = (
1254
+ f"Studio returned a run in state {state}. Identical run coordinates reuse "
1255
+ "the same run; this response does not establish that a new attempt was queued."
1256
+ )
1257
+ triple = failure_triple(run)
1258
+ if triple:
1259
+ payload["failure"] = triple
1260
+ payload["next_command"] = (
1261
+ f"mr-data run --retry {run['run_id']}"
1262
+ if triple["room_fault"]
1263
+ else f"mr-data status {run['run_id']}"
1264
+ )
1265
+ return payload
1246
1266
  except ThinLaneError as refusal:
1247
1267
  held_payload = _held_for_confirmation(refusal, body)
1248
1268
  if held_payload is None:
@@ -1271,7 +1291,12 @@ def start(args: argparse.Namespace, *, client: StudioV4RunClient | None = None)
1271
1291
  def run_exit_code(payload: Mapping[str, Any]) -> int:
1272
1292
  """A run held at the spend gate is a gate a script acts on, not a report it reads."""
1273
1293
 
1274
- return 2 if payload.get("status") == "run_awaiting_confirmation" else 0
1294
+ return (
1295
+ 2
1296
+ if payload.get("status") == "run_awaiting_confirmation"
1297
+ or payload.get("run", {}).get("status") in {"failed", "awaiting_confirmation"}
1298
+ else 0
1299
+ )
1275
1300
 
1276
1301
 
1277
1302
  # --------------------------------------------------------------------------------------------
@@ -487,6 +487,7 @@ STATUS_HEADLINES: dict[str, str] = {
487
487
  # what Studio's own state machine reached, and "the watch ended" is honest about the case
488
488
  # where the connection stopped before the run did.
489
489
  "run_queued": "The run is queued. Watch it at the address below.",
490
+ "run_returned": "Studio returned the run below in its current state.",
490
491
  # A run over the spend threshold is HELD, not refused and not started. Three words rather than
491
492
  # one, because "it is waiting for you", "you said yes and it is queued" and "it is stopped" are
492
493
  # three different things to have happened, and one word covering them is how a held run gets