mostlyright-data 0.30.0__tar.gz → 0.30.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 (132) hide show
  1. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/PKG-INFO +1 -1
  2. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/preparing-table-search.md +12 -8
  3. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/table_search_contract.json +1 -1
  4. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4.py +2 -2
  5. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_search.py +144 -75
  6. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/.gitignore +0 -0
  7. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/README.md +0 -0
  8. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/pyproject.toml +0 -0
  9. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/scripts/hatch_build.py +0 -0
  10. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/SKILL.md +0 -0
  11. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/agents/openai.yaml +0 -0
  12. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +0 -0
  13. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
  14. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
  15. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
  16. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
  17. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
  18. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
  19. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
  20. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
  21. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/agent-protocol.md +0 -0
  22. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
  23. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
  24. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/boundaries.md +0 -0
  25. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
  26. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/commands.md +0 -0
  27. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
  28. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/installation-parity.md +0 -0
  29. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/live-run.md +0 -0
  30. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/making-a-table-incremental.md +0 -0
  31. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/narrating-the-run.md +0 -0
  32. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
  33. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/one-install.md +0 -0
  34. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/patterns.md +0 -0
  35. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/prediction-labels.md +0 -0
  36. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/promote.md +0 -0
  37. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/readers.md +0 -0
  38. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/receipts.md +0 -0
  39. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/recipe-test.md +0 -0
  40. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
  41. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
  42. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/reference-pages.md +0 -0
  43. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/required-protocol.md +0 -0
  44. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/source-credentials.md +0 -0
  45. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/sources.md +0 -0
  46. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
  47. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/transforms.md +0 -0
  48. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/user-communication-contract.md +0 -0
  49. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
  50. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
  51. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/__init__.py +0 -0
  52. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/agent_protocol.py +0 -0
  53. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/canonical.py +0 -0
  54. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/formats.py +0 -0
  55. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
  56. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/key_seam.py +0 -0
  57. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/page_coverage.py +0 -0
  58. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
  59. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/session_probes.py +0 -0
  60. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/skill_assets.py +0 -0
  61. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/table_manifest.py +0 -0
  62. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/__init__.py +0 -0
  63. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/acquire.py +0 -0
  64. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
  65. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/activity.py +0 -0
  66. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/approvals.py +0 -0
  67. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/backoff.py +0 -0
  68. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/categories.py +0 -0
  69. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/commands.py +0 -0
  70. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
  71. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/download.py +0 -0
  72. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
  73. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/narrative.py +0 -0
  74. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/parity.py +0 -0
  75. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/probe.py +0 -0
  76. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
  77. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
  78. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/propose.py +0 -0
  79. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/recipe.py +0 -0
  80. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
  81. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/recipe_explain.py +0 -0
  82. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
  83. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
  84. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/recipe_test.py +0 -0
  85. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
  86. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/release_check.py +0 -0
  87. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/research.py +0 -0
  88. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/router.py +0 -0
  89. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
  90. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
  91. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/runs.py +0 -0
  92. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/session.py +0 -0
  93. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/stream.py +0 -0
  94. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
  95. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/studio_capabilities.py +0 -0
  96. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/support.py +0 -0
  97. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/transport.py +0 -0
  98. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
  99. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
  100. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
  101. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
  102. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
  103. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
  104. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
  105. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
  106. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
  107. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
  108. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
  109. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
  110. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
  111. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
  112. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
  113. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
  114. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
  115. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
  116. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/__init__.py +0 -0
  117. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/attendance.py +0 -0
  118. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/clarification.py +0 -0
  119. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
  120. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
  121. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
  122. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
  123. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
  124. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
  125. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
  126. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
  127. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/credentials.py +0 -0
  128. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/login.py +0 -0
  129. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
  130. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
  131. {mostlyright_data-0.30.0 → mostlyright_data-0.30.1}/src/mostlyright/data_harness/ux/remediation.py +0 -0
  132. {mostlyright_data-0.30.0 → mostlyright_data-0.30.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.30.0
3
+ Version: 0.30.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/
@@ -199,8 +199,8 @@ The `local` block lists each refusal with a JSON pointer. Repair every diagnosti
199
199
 
200
200
  Register and build the table as usual. A finished build of a table with `table.search` prepares
201
201
  search in the background. Data publication never waits for it. The live search package that
202
- `search try` and `search locate` use exists only for a table published publicly. Read the
203
- version ID from the run receipt, then check readiness:
202
+ `search try` uses exists only for a table published publicly. Read the version ID from the run
203
+ receipt, then check readiness:
204
204
 
205
205
  ```sh
206
206
  mr-data search status TABLE_ID --version VERSION_ID --json
@@ -280,12 +280,14 @@ mr-data search locate TABLE_ID --public-table PUBLIC_TABLE_ID --title "Dune" --y
280
280
  mr-data search locate TABLE_ID --public-table PUBLIC_TABLE_ID --id 27205 --json
281
281
  ```
282
282
 
283
- `locate` searches the live package and maps each matching row to its locator. It reads the
284
- title, date and identifier columns from the field roles. Override them with `--title-column`,
285
- `--date-column` and `--id-column`. Each column it reads must be in `display_columns`. A target
286
- with a year reads the date column, so declare `display_columns` with the date in it. A title
287
- without a year can match several rows. `locate` then lists them all and exits 4. Add the year or
288
- use the id. It writes `--out` only when every target resolved and the cases meet the contract.
283
+ `locate` reads the public table's rows and maps each matching row to its locator. It needs no
284
+ search package, so write the cases before the version has one. The table must be published
285
+ publicly with its preview on. It reads the title, date and identifier columns from the field
286
+ roles. Override them with `--title-column`, `--date-column` and `--id-column`. A title matches
287
+ exactly, with the same case and spacing as the cell. A year matches the start of the date
288
+ column's value. A title without a year can match several rows. `locate` then lists them all and
289
+ exits 4. Add the year or use the id. It writes `--out` only when every target resolved and the
290
+ cases meet the contract.
289
291
 
290
292
  Locators belong to one sealed version. `locate` resolves rows of the live version only. Configure
291
293
  the cases on that same version. A later version has a different row layout, so locate again after
@@ -313,6 +315,8 @@ Leave out `--spec` to declare the `table.search` spec of the recipe that sealed
313
315
  Use that to add evaluation cases without retyping the spec. Without `--spec` the client cannot
314
316
  check locally that semantic cases include a paraphrase and a hard negative. Studio checks them
315
317
  against the recipe's spec. A recipe with no `table.search` answers 409 `SEARCH_NOT_CONFIGURED`.
318
+ `search status --job` reads any search job: the build, its package and embed steps, the
319
+ evaluator and a title job. A job still `queued`, `dispatched` or `running` exits 3.
316
320
  `--expected-revision` is the current draft from `search status` (`selected_draft_revision_id`),
317
321
  or `none`. Reuse `--idempotency-key` only to retry the same configuration. When the job succeeds,
318
322
  `search status TABLE_ID --version VERSION_ID --revision REVISION_ID` reports `not_activated` and
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema_version": "mostlyright-thin-table-search-contract.v1",
3
- "source_commit": "a0555f0152fe14beefaa5e082c7e3352d052f87a",
3
+ "source_commit": "b308bc2e9ebb424b4cfe1130438ab9f7d0b6736d",
4
4
  "recipe_search_definition": "spec",
5
5
  "$defs": {
6
6
  "evaluation_case": {
@@ -148,8 +148,8 @@ from mostlyright.data_harness.thin.transport import ThinLaneError
148
148
  #: ``JOB_INVALID`` outright, which in production was every refresh a collection epoch was offered
149
149
  #: for rather than only the collection runs. This package must not reach a Studio older than the
150
150
  #: commit it pins; ``docs/V4-WORKER-PROTOCOL.md`` states it beside the layout's own ordering rule.
151
- PINNED_V4_OPENAPI_SOURCE_SHA256 = "fc5229095aacb4fb7fe57b34c206802b381c81088c40af9dc19c05889b486a41"
152
- PINNED_V4_CONTRACT_VERSION = "4.34.0"
151
+ PINNED_V4_OPENAPI_SOURCE_SHA256 = "001a5e132e8af9872ac61168e41df4465627f34ac062066b212f1b48a88385f4"
152
+ PINNED_V4_CONTRACT_VERSION = "4.35.0"
153
153
 
154
154
  #: The ``schema_version`` every command body that predates 4.14.0 stamps: ``dataset activity``,
155
155
  #: ``table rename``, ``table archive`` and ``dataset archive``. It is the number the 0.25 line
@@ -197,6 +197,7 @@ def search_exit_code(payload: dict[str, Any]) -> int:
197
197
  "queued",
198
198
  "dispatched",
199
199
  "running",
200
+ "deferred",
200
201
  "pending",
201
202
  "cancellation_requested",
202
203
  }:
@@ -217,6 +218,12 @@ def search_exit_code(payload: dict[str, Any]) -> int:
217
218
 
218
219
 
219
220
  SEARCH_JOB_SCHEMAS = {"table-search-job.v1", "table-search-job.v2"}
221
+ #: Every job Studio's job read answers. The whole-version (v1) and sharded (v2) parents carry
222
+ #: their schema; the title job carries v3; a package, embed or evaluation child carries none.
223
+ JOB_STATUS_SCHEMAS = {*SEARCH_JOB_SCHEMAS, "table-search-job.v3", None}
224
+ JOB_STATUSES = frozenset(
225
+ {"queued", "dispatched", "running", "deferred", "succeeded", "failed", "cancelled"}
226
+ )
220
227
 
221
228
  RECIPE_INSPECTION_PATH = "/v4/search/recipe-inspection"
222
229
  TABLE_SEARCH_PATH = "/v4/tables/{table_id}/search"
@@ -715,26 +722,60 @@ def _try(
715
722
  return _payload("try", result)
716
723
 
717
724
 
718
- def _locator(row_id: int, counts: list[int]) -> list[int]:
719
- start = 0
720
- for part, count in enumerate(counts):
721
- if row_id < start + count:
722
- return [part, row_id - start]
723
- start += count
724
- raise ThinLaneError("THIN_RESPONSE_INVALID", "a search row lies outside the sealed version")
725
+ #: Rows per page of a locate read. Cloud returns each row's physical locator only on a compact
726
+ #: page, and a compact page holds at most 50 rows.
727
+ LOCATE_PAGE_ROWS = 50
728
+ #: Pages one locate target may read before it reports the rows it found as ambiguous.
729
+ MAX_LOCATE_PAGES = 20
730
+
731
+
732
+ def _public_table(public_table_id: str, public: Any) -> dict[str, Any]:
733
+ """The public schema's table: which Studio version it serves, and whether it is queryable."""
734
+
735
+ from mostlyright.data_harness.thin.v4_public import PublicCatalogClient, resolve_public_origin
736
+
737
+ selected = public or PublicCatalogClient(resolve_public_origin())
738
+ body = selected.get_table_schema(public_table_id)
739
+ table = body.get("table") if isinstance(body, dict) else None
740
+ if not isinstance(table, dict):
741
+ raise ThinLaneError("THIN_RESPONSE_INVALID", "Cloud returned a schema without a table")
742
+ return table
725
743
 
726
744
 
727
745
  class _Locator:
728
- """Resolves rows of the published live version to evaluation-case locators."""
746
+ """Resolves rows of the published live version to evaluation-case locators.
747
+
748
+ It reads through Cloud's public table query, filtered on the title or identifier column, so
749
+ it needs no search package: cases are written before the version has one. Each row comes
750
+ back with its physical ``[part, row]`` locator. Every read is pinned to the version the first
751
+ one served, and that version must be the one Studio reports live."""
729
752
 
730
753
  def __init__(self, args, client, public, query_client) -> None:
731
754
  self.table_id = identifier(_required(args.target, "a table identifier"), "table identifier")
732
755
  self.public_table_id = identifier(args.public_table_id, "public table ID")
733
756
  self.studio = _studio(client)
734
- self.search, metadata = _bind_public(
735
- self.public_table_id, public, self.studio, self.table_id
736
- )
737
- self.version = metadata["table_version_id"]
757
+ table = _public_table(self.public_table_id, public)
758
+ metadata = _metadata(self.studio, self.table_id, None)
759
+ self.version = metadata.get("table_version_id")
760
+ capabilities = table.get("capabilities") or {}
761
+ search = capabilities.get("search") if isinstance(capabilities, dict) else None
762
+ served = {table.get("version_id")}
763
+ if isinstance(search, dict):
764
+ served.add(search.get("table_version_id"))
765
+ if not self.version or served != {self.version}:
766
+ raise ThinLaneError(
767
+ "THIN_SEARCH_VERSION_MISMATCH",
768
+ f"public table {self.public_table_id} serves version "
769
+ f"{table.get('version_id')}, but table {self.table_id} has live version "
770
+ f"{self.version}. Check --public-table with mr-data public tables DATASET_SLUG. "
771
+ "The public schema can lag a new live version by a few minutes, so retry then.",
772
+ )
773
+ if isinstance(capabilities, dict) and capabilities.get("query") is False:
774
+ raise ThinLaneError(
775
+ "THIN_SEARCH_CAPABILITY_UNAVAILABLE",
776
+ f"public table {self.public_table_id} does not serve queries, so locate cannot "
777
+ "read its rows. Turn on its preview in the dataset's publication settings.",
778
+ )
738
779
  if (
739
780
  args.table_version_id is not None
740
781
  and identifier(args.table_version_id, "table version") != self.version
@@ -751,22 +792,84 @@ class _Locator:
751
792
  self.id_column = args.id_column or roles.get("identifier")
752
793
  self.counts = _manifest_record_counts(self.studio, self.table_id, self.version)
753
794
  self.query_client = query_client
754
-
755
- def _rows(self, text: str, plan: dict[str, Any], columns: list[str]) -> list[dict[str, Any]]:
756
- answer, _ = _cloud_query(
757
- self.public_table_id,
758
- _resident_body(_query_text(text), self.search, columns, MAX_TRY_ROWS, plan),
759
- self.query_client,
760
- )
761
- if answer.get("path") != "resident" or answer.get("outcome") not in (None, "ok"):
795
+ self.pin: tuple[str, str] | None = None
796
+
797
+ def _bound(self, answer: dict[str, Any]) -> None:
798
+ served = answer.get("table")
799
+ if not isinstance(served, dict):
800
+ raise ThinLaneError("THIN_RESPONSE_INVALID", "Cloud returned rows without a table")
801
+ coordinates = (served.get("version_id"), served.get("content_digest"))
802
+ if not all(isinstance(value, str) and value for value in coordinates):
803
+ raise ThinLaneError("THIN_RESPONSE_INVALID", "Cloud returned rows without a version")
804
+ live = served.get("live_version_id")
805
+ rows = served.get("row_count")
806
+ if (
807
+ coordinates[0] != self.version
808
+ or live not in (None, self.version)
809
+ or (rows is not None and rows != sum(self.counts))
810
+ or (self.pin is not None and coordinates != self.pin)
811
+ ):
762
812
  raise ThinLaneError(
763
- "THIN_SEARCH_CAPABILITY_UNAVAILABLE",
764
- "the live search package did not answer ("
765
- + str(answer.get("note") or answer.get("outcome") or "no package")
766
- + f"). locate reads {', '.join(columns)}. Each must be in the spec's "
767
- "display_columns, and the package must be ready in mr-data search status.",
813
+ "THIN_SEARCH_VERSION_MISMATCH",
814
+ f"public table {self.public_table_id} stopped serving live version "
815
+ f"{self.version} while locate read it. Run locate again.",
768
816
  )
769
- return [row for row in answer.get("rows", []) if isinstance(row, dict)]
817
+ self.pin = coordinates
818
+
819
+ def _rows(self, filters: list[dict[str, Any]], columns: list[str]) -> tuple[list, bool]:
820
+ """Every row matching ``filters``, each with its locator, and whether pages remain."""
821
+
822
+ found: list[dict[str, Any]] = []
823
+ cursor: str | None = None
824
+ for _ in range(MAX_LOCATE_PAGES):
825
+ # A cursor is honoured only beside the query it was issued for, and it carries its
826
+ # own version pin.
827
+ body: dict[str, Any] = {
828
+ "columns": columns,
829
+ "filters": filters,
830
+ "limit": LOCATE_PAGE_ROWS,
831
+ "compact_cells": True,
832
+ }
833
+ if cursor is not None:
834
+ body["cursor"] = cursor
835
+ elif self.pin is not None:
836
+ body.update(version_id=self.pin[0], content_digest=self.pin[1])
837
+ answer, _ = _cloud_query(self.public_table_id, body, self.query_client)
838
+ self._bound(answer)
839
+ rows, refs = answer.get("rows"), answer.get("row_refs")
840
+ if (
841
+ not isinstance(rows, list)
842
+ or not isinstance(refs, list)
843
+ or len(rows) != len(refs)
844
+ or len(rows) > LOCATE_PAGE_ROWS
845
+ ):
846
+ raise ThinLaneError(
847
+ "THIN_RESPONSE_INVALID", "Cloud returned rows without their locators"
848
+ )
849
+ for row, ref in zip(rows, refs, strict=True):
850
+ part = ref.get("part_index") if isinstance(ref, dict) else None
851
+ index = ref.get("row_index") if isinstance(ref, dict) else None
852
+ if (
853
+ not isinstance(row, dict)
854
+ or type(part) is not int
855
+ or type(index) is not int
856
+ or not 0 <= part < len(self.counts)
857
+ or not 0 <= index < self.counts[part]
858
+ ):
859
+ raise ThinLaneError(
860
+ "THIN_RESPONSE_INVALID", "a located row lies outside the sealed version"
861
+ )
862
+ found.append(
863
+ {
864
+ "row_id": sum(self.counts[:part]) + index,
865
+ "locator": [part, index],
866
+ **row,
867
+ }
868
+ )
869
+ cursor = answer.get("next_cursor")
870
+ if not cursor:
871
+ return found, False
872
+ return found, True
770
873
 
771
874
  def resolve(self, target: Any) -> dict[str, Any]:
772
875
  """One draft target to its matches: ``{"title", "year"}``, ``{"id"}`` or a locator."""
@@ -796,23 +899,9 @@ class _Locator:
796
899
  value = target["id"]
797
900
  if self.types.get(column) == "integer" and isinstance(value, str) and value.isdigit():
798
901
  value = int(value)
799
- plan = {
800
- "schema_version": PLAN_SCHEMA,
801
- "kind": "browse",
802
- "text": "",
803
- "predicates": [
804
- {"column": column, "operator": "eq", "value": value, "origin": "explicit"}
805
- ],
806
- "prefer": [],
807
- "order": [],
808
- "scope": "matching_rows",
809
- "unresolved": [],
810
- }
811
- rows = [
812
- row
813
- for row in self._rows(str(target["id"]), plan, [column])
814
- if str((row.get("fields") or {}).get(column)) == str(value)
815
- ]
902
+ matches, more = self._rows(
903
+ [{"column": column, "operator": "eq", "value": value}], [column]
904
+ )
816
905
  else:
817
906
  column = _required(self.title_column, "--title-column (no title field role)")
818
907
  title = str(target["title"])
@@ -821,37 +910,18 @@ class _Locator:
821
910
  if year is not None:
822
911
  date = _required(self.date_column, "--date-column (no date field role)")
823
912
  columns.append(date)
824
- plan = {
825
- "schema_version": PLAN_SCHEMA,
826
- "kind": "title",
827
- "text": title,
828
- "predicates": [],
829
- "prefer": [],
830
- "order": [],
831
- "scope": "relevance_window",
832
- "unresolved": [],
833
- }
834
- rows = [
835
- row
836
- for row in self._rows(title, plan, list(dict.fromkeys(columns)))
837
- if _norm((row.get("fields") or {}).get(column)) == _norm(title)
838
- ]
913
+ matches, more = self._rows(
914
+ [{"column": column, "operator": "eq", "value": title}],
915
+ list(dict.fromkeys(columns)),
916
+ )
839
917
  if year is not None:
840
- rows = [
841
- row
842
- for row in rows
843
- if str((row.get("fields") or {}).get(date) or "").startswith(str(year))
918
+ matches = [
919
+ row for row in matches if str(row.get(date) or "").startswith(str(year))
844
920
  ]
845
- matches = [
846
- {
847
- "row_id": row["row_id"],
848
- "locator": _locator(row["row_id"], self.counts),
849
- **row["fields"],
850
- }
851
- for row in rows
852
- if type(row.get("row_id")) is int and isinstance(row.get("fields"), dict)
853
- ]
854
- status = "located" if len(matches) == 1 else "not_found" if not matches else "ambiguous"
921
+ if len(matches) == 1 and not more:
922
+ status = "located"
923
+ else:
924
+ status = "not_found" if not matches and not more else "ambiguous"
855
925
  return {
856
926
  "target": target,
857
927
  "status": status,
@@ -1057,9 +1127,8 @@ def search(
1057
1127
  if job_operation:
1058
1128
  if (
1059
1129
  answer.get("job_id") != job_id
1060
- or answer.get("status")
1061
- not in {"queued", "dispatched", "running", "succeeded", "failed", "cancelled"}
1062
- or (verb == "status" and answer.get("schema_version") not in SEARCH_JOB_SCHEMAS)
1130
+ or answer.get("status") not in JOB_STATUSES
1131
+ or (verb == "status" and answer.get("schema_version") not in JOB_STATUS_SCHEMAS)
1063
1132
  ):
1064
1133
  raise ThinLaneError(
1065
1134
  "THIN_RESPONSE_INVALID", "Studio returned invalid search job coordinates"