mostlyright-data 0.21.3__tar.gz → 0.22.1__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.3 → mostlyright_data-0.22.1}/PKG-INFO +1 -1
  2. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/pyproject.toml +1 -1
  3. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/skills/mr-data-build/SKILL.md +11 -3
  4. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4.py +1 -1
  5. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_artifacts.py +33 -21
  6. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/.gitignore +0 -0
  7. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/README.md +0 -0
  8. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/scripts/hatch_build.py +0 -0
  9. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/skills/mr-data-build/agents/openai.yaml +0 -0
  10. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  11. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/__init__.py +0 -0
  12. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/canonical.py +0 -0
  13. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/formats.py +0 -0
  14. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  15. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/key_seam.py +0 -0
  16. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/page_coverage.py +0 -0
  17. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  18. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/session_probes.py +0 -0
  19. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/skill_assets.py +0 -0
  20. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/table_manifest.py +0 -0
  21. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  22. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  23. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  24. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/activity.py +0 -0
  25. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  26. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/categories.py +0 -0
  27. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/commands.py +0 -0
  28. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  29. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/download.py +0 -0
  30. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  31. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/parity.py +0 -0
  32. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/probe.py +0 -0
  33. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  34. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/propose.py +0 -0
  35. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/recipe.py +0 -0
  36. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  37. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
  38. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/research.py +0 -0
  39. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/router.py +0 -0
  40. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/runs.py +0 -0
  41. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/session.py +0 -0
  42. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/stream.py +0 -0
  43. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  44. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/transport.py +0 -0
  45. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  46. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  47. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  48. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  49. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
  50. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  51. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  52. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  53. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
  54. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  55. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
  56. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
  57. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
  58. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  59. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  60. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  61. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  62. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  63. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  64. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  65. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  66. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  67. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  68. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  69. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  70. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/login.py +0 -0
  71. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  72. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  73. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/remediation.py +0 -0
  74. {mostlyright_data-0.21.3 → mostlyright_data-0.22.1}/src/mostlyright/data_harness/ux/render.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mostlyright-data
3
- Version: 0.21.3
3
+ Version: 0.22.1
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.21.3"
3
+ version = "0.22.1"
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
@@ -2024,6 +2031,7 @@ decoder. Zero matches and multiple matches both refuse.
2024
2031
  | `html.tabular@1.0.0` | Absolute record element path and relative text/attribute field selectors; fields are required unless `required: false`. |
2025
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. |
2026
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. |
2027
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. |
2028
2036
  | `parquet.tabular@1.0.0` | Optional `columns` selects a bounded ordered Parquet projection. |
2029
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. |
@@ -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
  # --------------------------------------------------------------------------------------------
@@ -277,9 +277,10 @@ class StudioV4ArtifactClient(StudioV4RunClient):
277
277
  ⚠ THE ONE ROUTE THAT REACHES A PART ANOTHER RUN SEALED. A version is a manifest over
278
278
  parts and the parts may come from many runs, so ``GET /v4/runs/{run_id}/artifacts``
279
279
  answers about one run's contribution and never about the whole table. This route is
280
- addressed by the version the run's OWN manifest names -- the coordinate is sealed inside
281
- the artifact this command already fetched -- so nothing here resolves a live pointer or
282
- asks whether the version is one anybody published.
280
+ addressed by the physical table and version identifiers on that public run. A sealed
281
+ manifest selects and verifies the parts, but legacy manifests can contain logical
282
+ identifiers that cannot address this route; nothing here resolves a live pointer or asks
283
+ whether the version is one anybody published.
283
284
  """
284
285
 
285
286
  body: dict[str, Any] = {"limit": PART_SESSION_PAGE_LIMIT}
@@ -548,19 +549,17 @@ def download(
548
549
  """``mr-data download RUN``: this run's artifacts, on this computer, each digest checked.
549
550
 
550
551
  ⚠ NOTHING ON THIS COMMAND'S OWN PATH ASKS WHETHER THE RUN MAY BE READ. The listing is fetched,
551
- a session is minted per artifact and the bytes are verified; the run record is never read on
552
- the way to the answer, so there is no place a status, a version or an approval could be
553
- consulted even by accident. That is REQ-H407's sentence made structural rather than promised.
554
-
555
- ⚠ AND THE SCOPE OF THAT SENTENCE IS THIS PATH, WHICH SAYING SO NARROWS RATHER THAN WEAKENS.
556
- After the answer is printed, ``thin.router`` makes one best-effort progress report, and for
557
- this command -- which holds a run identifier and nothing else -- that report reads
558
- ``GET /v4/runs/{run_id}`` to learn which dataset page to write on. It happens AFTER the
559
- download has been decided, executed and rendered, on its own bounded budget, and it cannot
560
- reach anything this command did or printed: nothing on the way to the artifacts consults the
561
- run record, which is the property REQ-H407 is about. A docstring claiming the record is never
562
- read at all would be false the first time somebody grepped for the route, and a false absolute
563
- is worse than a scoped true one.
552
+ a session is minted per artifact and the bytes are verified. A multipart version with retained
553
+ parts additionally reads the run's public table and version identifiers solely to address the
554
+ version-part session route; it never reads the status, approval, or promotion to decide whether
555
+ bytes may be read. That is REQ-H407's sentence made structural rather than promised.
556
+
557
+ ⚠ THE RUN RECORD'S NARROW ROLE IS TO ADDRESS, NEVER TO ADMIT. A manifest is a sealed logical
558
+ document and historical producers can put logical table identifiers in it. Studio's public
559
+ version-part route is addressed by the physical identifiers on the run record instead. Those
560
+ values are used only after the manifest identified retained parts that need signed sessions;
561
+ every fetched byte is still checked against the manifest's part digest. A status, approval, or
562
+ promotion value is neither read nor used as a predicate.
564
563
 
565
564
  ⚠ A TABLE VERSION ARRIVES WHOLE OR NOT AT ALL. The run listing answers about the parts THIS
566
565
  run sealed, and a version composed over several refreshes names parts earlier runs sealed; a
@@ -629,7 +628,7 @@ def _version_parts(
629
628
  sealed = {str(row.get("digest")): row for row in listing if row.get("kind") == "table_part"}
630
629
  repeated = Counter(part.part_key for part in manifest.parts)
631
630
  outstanding = tuple(part for part in manifest.parts if part.part_id not in sealed)
632
- minted = _part_sessions(client, manifest, outstanding)
631
+ minted = _part_sessions(client, run_id, manifest, outstanding)
633
632
  written: list[dict[str, Any]] = []
634
633
  for part in manifest.parts:
635
634
  destination = output / _part_destination(part, repeated=repeated[part.part_key] > 1)
@@ -660,19 +659,20 @@ def _part_destination(part: Any, *, repeated: bool) -> Path:
660
659
 
661
660
 
662
661
  def _part_sessions(
663
- client: StudioV4ArtifactClient, manifest: Any, outstanding: Sequence[Any]
662
+ client: StudioV4ArtifactClient, run_id: str, manifest: Any, outstanding: Sequence[Any]
664
663
  ) -> dict[str, dict[str, Any]]:
665
664
  """One signed read per part this run did not seal, or a refusal naming how many are missing."""
666
665
 
667
666
  if not outstanding:
668
667
  return {}
668
+ public_run = StudioV4RunClient(client.session, transport=client.transport).run(run_id)
669
+ table_id = _run_identifier(public_run, "table_id")
670
+ version_id = _run_identifier(public_run, "table_version_id")
669
671
  wanted = {part.part_id for part in outstanding}
670
672
  minted: dict[str, dict[str, Any]] = {}
671
673
  cursor: str | None = None
672
674
  for _page in range(MAX_PART_SESSION_PAGES):
673
- page = client.version_part_sessions(
674
- manifest.table_id, manifest.table_version_id, cursor=cursor
675
- )
675
+ page = client.version_part_sessions(table_id, version_id, cursor=cursor)
676
676
  entries = page.get("parts")
677
677
  if not isinstance(entries, list):
678
678
  raise ThinLaneError(
@@ -701,6 +701,18 @@ def _part_sessions(
701
701
  return minted
702
702
 
703
703
 
704
+ def _run_identifier(run: Mapping[str, Any], field: str) -> str:
705
+ """One public physical version coordinate, never a lifecycle predicate."""
706
+
707
+ value = run.get(field)
708
+ if not isinstance(value, str) or not value:
709
+ raise ThinLaneError(
710
+ "THIN_RESPONSE_INVALID",
711
+ f"the run's public {field} is absent while retained table parts need signed reads",
712
+ )
713
+ return identifier(value, f"run {field}")
714
+
715
+
704
716
  def _fetch_version_part(
705
717
  client: StudioV4ArtifactClient,
706
718
  part: Any,