mostlyright-data 0.31.12__tar.gz → 0.31.14__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.
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/PKG-INFO +8 -1
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/README.md +7 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/commands.md +1 -1
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/patterns.md +3 -2
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/readers.md +12 -2
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/sources.md +4 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/recipe.py +2 -2
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/router.py +2 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/runs.py +2 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/session.py +28 -1
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/stream.py +2 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/support.py +32 -8
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/table_search_contract.json +8 -2
- mostlyright_data-0.31.14/src/mostlyright/data_harness/thin/user_agent.py +234 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4.py +2 -2
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_datasets.py +19 -6
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_public.py +3 -1
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_query.py +95 -6
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_reader.py +26 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_search.py +3 -1
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/attendance.py +11 -6
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/cloud_auth.py +2 -2
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/commands/clarify.py +10 -5
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/login.py +7 -2
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/remediation.py +12 -1
- mostlyright_data-0.31.12/src/mostlyright/data_harness/thin/user_agent.py +0 -76
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/.gitignore +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/pyproject.toml +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/scripts/hatch_build.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/SKILL.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/agents/openai.yaml +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/agent-protocol.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/boundaries.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/enriching-with-small-models.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/installation-parity.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/live-run.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/making-a-table-incremental.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/narrating-the-run.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/one-install.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/prediction-labels.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/preparing-table-search.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/promote.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/receipts.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/recipe-test.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/reference-pages.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/required-protocol.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/source-credentials.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/transforms.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/user-communication-contract.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/__init__.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/agent_protocol.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/canonical.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/formats.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/key_seam.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/page_coverage.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/session_probes.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/skill_assets.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/table_manifest.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/__init__.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/acquire.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/activity.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/approvals.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/backoff.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/categories.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/commands.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/delivery.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/description_shape.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/download.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/enrichment.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/matching_benchmark.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/narrative.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/parity.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/probe.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/propose.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/recipe_explain.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/recipe_test.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/release_check.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/research.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/studio_capabilities.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/table_view.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/transport.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_matching.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/__init__.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/clarification.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/credentials.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
- {mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/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.31.
|
|
3
|
+
Version: 0.31.14
|
|
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/
|
|
@@ -46,6 +46,13 @@ Once a day, after a command has printed its answer, `mr-data` asks the Python pa
|
|
|
46
46
|
a newer release is published and says so in one line on stderr. `MOSTLYRIGHT_RELEASE_CHECK=0`
|
|
47
47
|
switches that off.
|
|
48
48
|
|
|
49
|
+
Every call to Mostly Right names the coding agent running `mr-data` in `X-Mostlyright-Agent`
|
|
50
|
+
(`claude-code`, `codex`, `gemini-cli`, `cursor`, `ci` or `none`, read from the variables those
|
|
51
|
+
hosts set), and a program that imports this package sends it too. An `mr-data` command also sends
|
|
52
|
+
one random id in `X-Mostlyright-Invocation`, so the requests of one command can be grouped; a
|
|
53
|
+
library call sends none. Neither is sent to storage hosts. `MOSTLYRIGHT_NO_AGENT_HEADERS=1`
|
|
54
|
+
switches both off.
|
|
55
|
+
|
|
49
56
|
## Sign in
|
|
50
57
|
|
|
51
58
|
```bash
|
|
@@ -31,6 +31,13 @@ Once a day, after a command has printed its answer, `mr-data` asks the Python pa
|
|
|
31
31
|
a newer release is published and says so in one line on stderr. `MOSTLYRIGHT_RELEASE_CHECK=0`
|
|
32
32
|
switches that off.
|
|
33
33
|
|
|
34
|
+
Every call to Mostly Right names the coding agent running `mr-data` in `X-Mostlyright-Agent`
|
|
35
|
+
(`claude-code`, `codex`, `gemini-cli`, `cursor`, `ci` or `none`, read from the variables those
|
|
36
|
+
hosts set), and a program that imports this package sends it too. An `mr-data` command also sends
|
|
37
|
+
one random id in `X-Mostlyright-Invocation`, so the requests of one command can be grouped; a
|
|
38
|
+
library call sends none. Neither is sent to storage hosts. `MOSTLYRIGHT_NO_AGENT_HEADERS=1`
|
|
39
|
+
switches both off.
|
|
40
|
+
|
|
34
41
|
## Sign in
|
|
35
42
|
|
|
36
43
|
```bash
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/commands.md
RENAMED
|
@@ -17,7 +17,7 @@ run in full.
|
|
|
17
17
|
| `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. |
|
|
18
18
|
| `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. **It rides the frozen `/v3/sessions` routes, which a deployment may have switched off, so do not plan a source inspection around it.** Both positionals are identifiers. `QUESTION_ID` is a question identifier, which no v4 registration receipt returns. The v4 way to look at a source before registering it is `mr-data source inspect`, in the next row. The other way to read one is a bounded run of a scratch table, never of the real table, as [the probe rule](3-probe-read-a-source-before-committing-to-it.md#the-probe-rule-test-on-a-scratch-table-never-on-the-real-table) says. |
|
|
19
19
|
| `mr-data source` | `source inspect` takes a bounded look at one source, and it is the v4 path where `probe` is not. [Stage 3](3-probe-read-a-source-before-committing-to-it.md) says when to use it. It starts one of three ways, and each prints the `session_id`, `probe_id` and `source_id`, plus the `dataset_id` and `question_id` when it has them. For a new public HTTPS CSV that needs no key, give `--dataset-id`, `--question`, `--source-name`, `--source-url`, `--claimed-basis` and `--claim-evidence-digest`: it prepares the source and opens a session. For a source already prepared, give `--dataset-id`, `--question-id` and `--source-id`, which opens a session. In a session you already hold, give `--session-id` with `--source-id`. `--context-source-id` adds a source to a session it opens. `--session-id` with `--probe-id` reads the answer back. A look is bounded to 65,536 bytes, 1,000 rows and 300 seconds. `queued` and `truncated` are not complete answers, and `capability_unavailable` means this deployment cannot inspect, so read the source by a bounded run of a scratch table instead, as [the probe rule](3-probe-read-a-source-before-committing-to-it.md#the-probe-rule-test-on-a-scratch-table-never-on-the-real-table) says. What it finds is exploratory evidence, not a run and not a receipt. |
|
|
20
|
-
| `mr-data reader` | `reader normalize --family-id ID --family-version VERSION --options-file FILE [--workspace-id ID]` asks Studio's Reader service to apply one certified family's own defaults and option rules to a JSON object of options, at most 64 KiB, and prints the normalized `decode_options` and `
|
|
20
|
+
| `mr-data reader` | `reader normalize --family-id ID --family-version VERSION --options-file FILE [--workspace-id ID]` asks Studio's Reader service to apply one certified family's own defaults and option rules to a JSON object of options, at most 64 KiB, and prints the normalized `decode_options`, `decode_options_json` and `options_digest`. The second is the text a recipe carries as `reader.decode_options`, and the third is the `reader.options_digest` registration accepts for exactly those options, pinned as [Readers](readers.md) describes. It changes nothing, and this client does not validate or invent a family's options: Studio decides. Run it for every Reader pin before you register, so a wrong option is refused here and not inside a run. |
|
|
21
21
|
| `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. |
|
|
22
22
|
| `mr-data public` | Read Mostly Right's public dataset catalogue. `public search "QUERY"` ranks published datasets (`--topic`, `--limit` and `--cursor` narrow and page it), `show SLUG` reads one overview, `tables SLUG` lists its table identifiers, `schema TABLE_ID` reads one table's schema, `sample TABLE_ID` prints its published preview rows, `lookup "QUERY"` is the connector search shape and `fetch SLUG` returns the dataset's Markdown document. Those seven need no login. `query TABLE_ID`, with `--body` or `--body-file`, and `download-url TABLE_ID` do need `mr-data login`: they ask Cloud, with the device key, to run one query over a public table and to say how it can be downloaded. Preview rows are not a query, and no table bytes pass through the command. |
|
|
23
23
|
| `mr-data connected` | `connected list` shows the public datasets connected to this workspace and `connected add SLUG` connects one by its slug. It needs `mr-data login`: both ask Cloud, with the device key, over the routes MCP's `list_connected_datasets` and `connect_dataset` use. |
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/patterns.md
RENAMED
|
@@ -225,8 +225,9 @@ for the days that can still change, through the two request `parameters` it rend
|
|
|
225
225
|
unit reads its own page count, so size `limits.max_requests` from a measured unit.
|
|
226
226
|
[Paging a keyed API in parallel](sources.md#paging-a-keyed-api-in-parallel) has the rules.
|
|
227
227
|
|
|
228
|
-
`options_digest` is the SHA-256 of the canonical JSON of `decode_options
|
|
229
|
-
|
|
228
|
+
`options_digest` is the SHA-256 of the canonical JSON of `decode_options`, and
|
|
229
|
+
`mr-data reader normalize` prints it for the options it returns. Registration names the digest it
|
|
230
|
+
expected when the two disagree.
|
|
230
231
|
|
|
231
232
|
```json
|
|
232
233
|
{
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/readers.md
RENAMED
|
@@ -57,9 +57,17 @@ mr-data reader normalize --family-id json.tabular --family-version 1.1.0 \
|
|
|
57
57
|
|
|
58
58
|
`options.json` holds the keys the source needs, at most 64 KiB. Studio applies the family's own
|
|
59
59
|
defaults and option rules. The answer's `decode_options_json` is the text to write as
|
|
60
|
-
`reader.decode_options
|
|
60
|
+
`reader.decode_options`, and its `options_digest` is the `reader.options_digest` to pin beside it.
|
|
61
|
+
That digest binds the default-filled options the answer returns, not the subset in `options.json`,
|
|
62
|
+
so copy both together. A refusal arrives here, before any run is spent. Run it for every
|
|
61
63
|
Reader pin before you register.
|
|
62
64
|
|
|
65
|
+
`normalize` checks the options against the family, not against the place the pin goes. A
|
|
66
|
+
Generic API `generic_api.response.reader` admits fewer coordinates: `json.tabular` `1.0.0`,
|
|
67
|
+
`1.1.0` or `1.2.0`, `delimited_text` `1.0.0` or `1.1.0`, and `parquet.tabular` `1.0.0`.
|
|
68
|
+
`json.tabular` `1.3.0` and `1.4.0` normalize but are refused there. The highest-version advice
|
|
69
|
+
below applies inside that set.
|
|
70
|
+
|
|
63
71
|
A column that may be absent from a record must state `"required": false`. Publishers routinely omit
|
|
64
72
|
a key rather than sending null, and a required column that is missing refuses the record.
|
|
65
73
|
|
|
@@ -87,6 +95,7 @@ decoder. Zero matches and multiple matches both refuse.
|
|
|
87
95
|
| `archive.zip@1.0.0` | `member`; `member_format` (`csv` or `tsv`); optional `header: true`. |
|
|
88
96
|
| `archive.zip@1.1.0` | Same, plus `member_format: semicolon` for publishers such as DWD. |
|
|
89
97
|
| `archive.zip@1.2.0` | Same as `archive.zip@1.1.0`; also accepts a response served as `application/octet-stream`. |
|
|
98
|
+
| `archive.zip@4.0.0` | Exact safe `member`, `member_format: lines` and `encoding: utf-8 or cp1252`; no header. Streams the member's physical lines as line_number, raw_line, as `archive.tar@2.0.0` does for one member. |
|
|
90
99
|
| `archive.gzip@1.0.0` | `member_format`; optional `header: true`. `member` is invalid. |
|
|
91
100
|
| `archive.gzip@1.1.0` | Same; also accepts a response served as `application/octet-stream`. |
|
|
92
101
|
| `archive.tar@1.0.0` | Same as `archive.zip@1.0.0`. |
|
|
@@ -181,7 +190,8 @@ Use `public.https.api@1.0.0` only when `keyless_window` is literal `true`. Other
|
|
|
181
190
|
The new adapter uses the existing `generic_api.response` Reader contract. For Binance klines,
|
|
182
191
|
state `format: json`, `records_pointer: ""`, and `reader.family_id: json.tabular` with
|
|
183
192
|
`family_version: 1.0.0` (or the already admitted `1.1.0`). Pin `options_digest` to the SHA-256
|
|
184
|
-
of the canonical decode options,
|
|
193
|
+
of the canonical decode options, the `options_digest` `mr-data reader normalize` prints, rather
|
|
194
|
+
than inserting a placeholder digest. These decode
|
|
185
195
|
options project a root JSON array of arrays, with RFC 6901 numeric indexes into each row:
|
|
186
196
|
|
|
187
197
|
```json
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/skills/mr-data-build/references/sources.md
RENAMED
|
@@ -335,6 +335,10 @@ to separate them. No per-row collection may derive its rows from a `closed` or `
|
|
|
335
335
|
`pagination` is a `page` or `offset` walk with a required `parallel` member, in one of two forms.
|
|
336
336
|
Prefer the counted form.
|
|
337
337
|
|
|
338
|
+
`@2.1.0` refuses `{"kind": "none", "max_pages": 1}`, and `cursor` and `link` walks too. For one
|
|
339
|
+
request, use `@2.0.0`. Use `authenticated.https.connector@3.0.0` instead when the request
|
|
340
|
+
declares a `per_call` cost, which `@2.0.0` does not admit.
|
|
341
|
+
|
|
338
342
|
| Form | Members | Use it when |
|
|
339
343
|
| --- | --- | --- |
|
|
340
344
|
| counted | `total_pages_pointer`, `max_page_bytes`, `min_interval_milliseconds`, optional `concurrency` | the first page states how many pages exist. A `page` walk over a `json` response only |
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/recipe.py
RENAMED
|
@@ -170,12 +170,12 @@ FILE_DECODER_COORDINATES: frozenset[tuple[str, str]] = frozenset(
|
|
|
170
170
|
for family, versions in {
|
|
171
171
|
"archive.gzip": ("1.0.0", "1.1.0", "2.0.0", "2.1.0"),
|
|
172
172
|
"archive.tar": ("1.0.0", "1.1.0", "3.0.0", "4.0.0"),
|
|
173
|
-
"archive.zip": ("1.0.0", "1.1.0", "1.2.0", "1.3.0", "1.4.0", "1.5.0", "3.0.0"),
|
|
173
|
+
"archive.zip": ("1.0.0", "1.1.0", "1.2.0", "1.3.0", "1.4.0", "1.5.0", "3.0.0", "4.0.0"),
|
|
174
174
|
"delimited_text": ("1.0.0", "1.1.0", "2.0.0"),
|
|
175
175
|
"geoparquet.vector": ("1.0.0",),
|
|
176
176
|
"json.tabular": ("1.1.0", "1.2.0"),
|
|
177
177
|
"meteorology.bufr": ("1.0.0",),
|
|
178
|
-
"meteorology.grib": ("1.0.0",),
|
|
178
|
+
"meteorology.grib": ("1.0.0", "2.0.0"),
|
|
179
179
|
"microdata.sas_xpt": ("1.0.0",),
|
|
180
180
|
"microdata.spss_sav": ("1.0.0",),
|
|
181
181
|
"microdata.stata_dta": ("1.0.0",),
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/router.py
RENAMED
|
@@ -52,6 +52,7 @@ from collections.abc import Callable, Mapping, Sequence
|
|
|
52
52
|
from typing import Any
|
|
53
53
|
|
|
54
54
|
from mostlyright.data_harness import skill_assets
|
|
55
|
+
from mostlyright.data_harness.thin import user_agent
|
|
55
56
|
from mostlyright.data_harness.thin.transport import ThinLaneError
|
|
56
57
|
from mostlyright.data_harness.thin.vocabulary import (
|
|
57
58
|
CLASSIFICATION,
|
|
@@ -781,6 +782,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
781
782
|
# not write, it never raises, and `MOSTLYRIGHT_SKILL_AUTOINSTALL=0` turns it off. A skill that
|
|
782
783
|
# arrived only when somebody knew to ask for it helped exactly the people who did not need it.
|
|
783
784
|
skill_assets.ensure_installed()
|
|
785
|
+
user_agent.begin_invocation()
|
|
784
786
|
|
|
785
787
|
arguments = list(sys.argv[1:] if argv is None else argv)
|
|
786
788
|
hosted_requested = _hosted_flag_present(arguments)
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/runs.py
RENAMED
|
@@ -46,6 +46,7 @@ from mostlyright.data_harness.thin.transport import (
|
|
|
46
46
|
UrlLibTransport,
|
|
47
47
|
signed_transfer_url,
|
|
48
48
|
)
|
|
49
|
+
from mostlyright.data_harness.thin.user_agent import agent_headers
|
|
49
50
|
|
|
50
51
|
#: The contract version every payload below is written against. ``studio_boundary`` pins the
|
|
51
52
|
#: generated client to the same 3.0.0 and to one OpenAPI source digest; this constant is the thin
|
|
@@ -279,6 +280,7 @@ class StudioApiClient:
|
|
|
279
280
|
headers = {
|
|
280
281
|
"Accept": "application/json",
|
|
281
282
|
CLIENT_HEADER: CLIENT_NAME,
|
|
283
|
+
**agent_headers(),
|
|
282
284
|
}
|
|
283
285
|
if extra_headers:
|
|
284
286
|
headers.update(extra_headers)
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/session.py
RENAMED
|
@@ -42,6 +42,7 @@ from mostlyright.data_harness.thin.transport import (
|
|
|
42
42
|
UrlLibTransport,
|
|
43
43
|
service_origin,
|
|
44
44
|
)
|
|
45
|
+
from mostlyright.data_harness.thin.user_agent import agent_headers
|
|
45
46
|
from mostlyright.data_harness.ux.credentials import ResolvedCloudCredentials
|
|
46
47
|
|
|
47
48
|
#: Cloud's token-exchange route. Held identical to ``hosted_deploy.CLOUD_TOKEN_PATH`` by a test.
|
|
@@ -396,7 +397,7 @@ def open_studio_session(
|
|
|
396
397
|
status, raw, headers = selected.request(
|
|
397
398
|
"POST",
|
|
398
399
|
f"{cloud_url}{CLOUD_TOKEN_PATH}",
|
|
399
|
-
{"Accept": "application/json", "x-api-key": credentials.raw_key},
|
|
400
|
+
{"Accept": "application/json", "x-api-key": credentials.raw_key, **agent_headers()},
|
|
400
401
|
b"",
|
|
401
402
|
MAX_TOKEN_RESPONSE_BYTES,
|
|
402
403
|
)
|
|
@@ -586,6 +587,19 @@ def sessions_for(session: StudioSession | StudioSessionProvider) -> StudioSessio
|
|
|
586
587
|
#: a sentence with spaces in it.
|
|
587
588
|
CLOUD_REFUSAL_CODE = re.compile(r"[A-Z][A-Z0-9_]{0,63}")
|
|
588
589
|
|
|
590
|
+
#: Cloud's refusal of a web chat sandbox key on a route that would change something
|
|
591
|
+
#: (Cloud ``lib/sandbox-key-refusal.ts``). The key is valid and reads; every change it
|
|
592
|
+
#: would make goes through the chat's own approval-gated tools or a person on the dashboard.
|
|
593
|
+
#: Folded onto ``THIN_AUTHORIZATION_DENIED`` it told an agent in the web chat to run
|
|
594
|
+
#: ``mr-data login``, which cannot help, so it keeps its own code -- and Cloud's sentence,
|
|
595
|
+
#: which names the chat tool that does the job.
|
|
596
|
+
SANDBOX_KEY_READ_ONLY_CODE = "SANDBOX_KEY_READ_ONLY"
|
|
597
|
+
THIN_SANDBOX_KEY_READ_ONLY = f"THIN_CLOUD_{SANDBOX_KEY_READ_ONLY_CODE}"
|
|
598
|
+
SANDBOX_KEY_READ_ONLY_SENTENCE = (
|
|
599
|
+
"This is a web chat key: it reads, and changes go through the chat's own tools or a "
|
|
600
|
+
"person on the dashboard; signing in again will not change that"
|
|
601
|
+
)
|
|
602
|
+
|
|
589
603
|
#: The family of Studio's refusals of a call that would skip Cloud's usage meter
|
|
590
604
|
#: (``DATA_EGRESS_THROUGH_CLOUD``). From a Cloud route it means Studio refused CLOUD's call, which
|
|
591
605
|
#: is Mostly Right's misconfiguration and never the caller's key.
|
|
@@ -843,6 +857,18 @@ def cloud_refusal(
|
|
|
843
857
|
"this computer's or the device key's; nothing here needs to change. Run the command "
|
|
844
858
|
"again later",
|
|
845
859
|
)
|
|
860
|
+
elif status == 403 and named == SANDBOX_KEY_READ_ONLY_CODE:
|
|
861
|
+
# NOT A MISSING OR EXPIRED KEY, so not the shared authorization code: the chat's
|
|
862
|
+
# key is valid and only this change is not its to make. Cloud's sentence names the
|
|
863
|
+
# tool that makes it, so it is quoted (bounded, printable, attributed) even though a
|
|
864
|
+
# code was named, which no other named refusal here does.
|
|
865
|
+
stated_alternative = _cloud_sentence(parsed.get("error"))
|
|
866
|
+
refusal = ThinLaneError(
|
|
867
|
+
THIN_SANDBOX_KEY_READ_ONLY,
|
|
868
|
+
f"{SANDBOX_KEY_READ_ONLY_SENTENCE}. Cloud said: {stated_alternative}"
|
|
869
|
+
if stated_alternative
|
|
870
|
+
else SANDBOX_KEY_READ_ONLY_SENTENCE,
|
|
871
|
+
)
|
|
846
872
|
elif status in {401, 403}:
|
|
847
873
|
if named:
|
|
848
874
|
suffix = f" (Cloud said {named}, HTTP {status})"
|
|
@@ -953,6 +979,7 @@ class CloudClient:
|
|
|
953
979
|
headers = {
|
|
954
980
|
"Accept": "application/json",
|
|
955
981
|
"Authorization": f"Bearer {self._credentials.raw_key}",
|
|
982
|
+
**agent_headers(),
|
|
956
983
|
}
|
|
957
984
|
encoded: bytes | None = None
|
|
958
985
|
if body is not None:
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/stream.py
RENAMED
|
@@ -49,6 +49,7 @@ from mostlyright.data_harness.thin.session import (
|
|
|
49
49
|
stated_sentence,
|
|
50
50
|
)
|
|
51
51
|
from mostlyright.data_harness.thin.transport import ThinLaneError, Transport, UrlLibTransport
|
|
52
|
+
from mostlyright.data_harness.thin.user_agent import agent_headers
|
|
52
53
|
|
|
53
54
|
#: Upstream's frame names. The first two are the tick/proof partition; ``end`` is its
|
|
54
55
|
#: end-of-stream notice.
|
|
@@ -593,6 +594,7 @@ class RunEventStream:
|
|
|
593
594
|
headers = {
|
|
594
595
|
"Accept": "text/event-stream",
|
|
595
596
|
CLIENT_HEADER: CLIENT_NAME,
|
|
597
|
+
**agent_headers(),
|
|
596
598
|
**session.authorization(),
|
|
597
599
|
}
|
|
598
600
|
# Resume in Studio's own cursor space when we have one. `from_seq` is only ever the
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/support.py
RENAMED
|
@@ -31,7 +31,11 @@ from typing import Any
|
|
|
31
31
|
|
|
32
32
|
from mostlyright.data_harness.thin import THIN_SCHEMA_PREFIX
|
|
33
33
|
from mostlyright.data_harness.thin.backoff import jittered
|
|
34
|
-
from mostlyright.data_harness.thin.session import
|
|
34
|
+
from mostlyright.data_harness.thin.session import (
|
|
35
|
+
THIN_SANDBOX_KEY_READ_ONLY,
|
|
36
|
+
CloudClient,
|
|
37
|
+
require_device_key,
|
|
38
|
+
)
|
|
35
39
|
from mostlyright.data_harness.thin.transport import ThinLaneError, UrlLibTransport
|
|
36
40
|
from mostlyright.data_harness.ux.credentials import resolve_cloud_credentials
|
|
37
41
|
|
|
@@ -61,6 +65,14 @@ SETTLED = frozenset({"answered", "budget_exhausted", "failed"})
|
|
|
61
65
|
SETTLED_RECEIPTS = frozenset(f"support_{state}" for state in SETTLED)
|
|
62
66
|
STATUS_VERB = "status"
|
|
63
67
|
|
|
68
|
+
#: What a web chat key is told when Cloud refuses it this route. Said here as well as by
|
|
69
|
+
#: Cloud, so an agent in the chat learns the tool even if Cloud's sentence did not survive
|
|
70
|
+
#: the bound on quoted text.
|
|
71
|
+
CHAT_SUPPORT_TOOLS = (
|
|
72
|
+
"In the web chat, ask with the chat's ask_support tool, then read the answer with "
|
|
73
|
+
"get_support_answer"
|
|
74
|
+
)
|
|
75
|
+
|
|
64
76
|
|
|
65
77
|
def declare_arguments(parser: argparse.ArgumentParser) -> None:
|
|
66
78
|
"""Add `ask`'s arguments to the shared hosted parser."""
|
|
@@ -106,17 +118,29 @@ def declare_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
106
118
|
)
|
|
107
119
|
|
|
108
120
|
|
|
121
|
+
def _name_chat_tools(failure: ThinLaneError) -> ThinLaneError:
|
|
122
|
+
"""A web chat key refused here is pointed at the chat's own support tools."""
|
|
123
|
+
|
|
124
|
+
if failure.code == THIN_SANDBOX_KEY_READ_ONLY and "ask_support" not in failure.detail:
|
|
125
|
+
failure.detail = f"{failure.detail}. {CHAT_SUPPORT_TOOLS}"
|
|
126
|
+
failure.args = (f"{failure.code}: {failure.detail}",)
|
|
127
|
+
return failure
|
|
128
|
+
|
|
129
|
+
|
|
109
130
|
class SupportClient(CloudClient):
|
|
110
131
|
"""The two support routes over the shared device-key Cloud client."""
|
|
111
132
|
|
|
112
133
|
def submit(self, body: Mapping[str, Any]) -> dict[str, Any]:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
try:
|
|
135
|
+
return self.call(
|
|
136
|
+
"POST",
|
|
137
|
+
CLOUD_SUPPORT_QUESTIONS_PATH,
|
|
138
|
+
act="ask the support agent a question",
|
|
139
|
+
body=body,
|
|
140
|
+
expected=(202,),
|
|
141
|
+
)
|
|
142
|
+
except ThinLaneError as failure:
|
|
143
|
+
raise _name_chat_tools(failure) from None
|
|
120
144
|
|
|
121
145
|
def read(self, question_id: str, *, run: bool) -> dict[str, Any]:
|
|
122
146
|
suffix = "" if run else "?wait=0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "mostlyright-thin-table-search-contract.v1",
|
|
3
|
-
"source_commit": "
|
|
3
|
+
"source_commit": "b296c21484f40cce334e1b7c0f6c7c03b44a41e9",
|
|
4
4
|
"recipe_search_definition": "recipe_search",
|
|
5
5
|
"$defs": {
|
|
6
6
|
"common.safe_integer": {
|
|
@@ -234,6 +234,12 @@
|
|
|
234
234
|
"asc",
|
|
235
235
|
"desc"
|
|
236
236
|
]
|
|
237
|
+
},
|
|
238
|
+
"quality": {
|
|
239
|
+
"description": "Optional owner quality note (4.55.0). A string of 1 to 200 characters. Shown on the order question only when this phrase occurs in the query. It does not change the sort. A boolean is invalid.",
|
|
240
|
+
"type": "string",
|
|
241
|
+
"minLength": 1,
|
|
242
|
+
"maxLength": 200
|
|
237
243
|
}
|
|
238
244
|
},
|
|
239
245
|
"required": [
|
|
@@ -242,7 +248,7 @@
|
|
|
242
248
|
"direction"
|
|
243
249
|
]
|
|
244
250
|
},
|
|
245
|
-
"description": "Order phrases (4.45.0: at most 256), one package order key each, under the aliases' shared byte budget."
|
|
251
|
+
"description": "Order phrases (4.45.0: at most 256), one package order key each, under the aliases' shared byte budget. Optional quality (4.55.0) is the owner's note for that phrase."
|
|
246
252
|
},
|
|
247
253
|
"default_order": {
|
|
248
254
|
"description": "The order of a search with nothing to rank by (4.44.0): a browse or an entity plan with no order of its own walks the rows in this order instead of the package's row order (popularity). Mojirama's `known` (vote count descending) for a film table; the column must be sortable (declared sortable, a sort alias's, or the popularity or votes role).",
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""The identity every first-party request to Mostly Right states.
|
|
2
|
+
|
|
3
|
+
Without it the standard library sends ``Python-urllib/3.x``, which is what every unattended
|
|
4
|
+
script on the internet sends: the CLI's own traffic could not be told apart from anything else in
|
|
5
|
+
a server log, so nothing downstream could measure it, rate-limit it separately, or exempt it.
|
|
6
|
+
|
|
7
|
+
The name follows the convention the two crawler lanes already use -- a product token, a version,
|
|
8
|
+
and the contact segment that says where to reach the people running it -- so an origin and our own
|
|
9
|
+
logs see one family of identities from this deployment rather than one per code path.
|
|
10
|
+
|
|
11
|
+
Two more headers ride beside it on first-party calls only (:func:`agent_headers`): which coding
|
|
12
|
+
agent, if any, is driving this process, and, under the ``mr-data`` command, one id shared by every
|
|
13
|
+
request that command makes. They go where ``X-Mostlyright-Client`` goes and on Cloud's own
|
|
14
|
+
routes, never through the transport, for the reason ``thin.runs.CLIENT_HEADER`` gives: the
|
|
15
|
+
transport also carries signed transfers to storage nobody here owns.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import secrets
|
|
22
|
+
from collections.abc import Mapping
|
|
23
|
+
from functools import cache
|
|
24
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
25
|
+
from typing import NamedTuple
|
|
26
|
+
|
|
27
|
+
#: The contact segment ``hosted_worker.CRAWLER_CONTACT_SEGMENT`` states for the keyed-source
|
|
28
|
+
#: agents and ``hosted_crawler_fetch.PUBLIC_HTTPS_USER_AGENT`` carries for public sources, with
|
|
29
|
+
#: the measurement that makes it load-bearing recorded at the first of those.
|
|
30
|
+
#:
|
|
31
|
+
#: It is spelled again here rather than imported because both of those modules import ``pyarrow``
|
|
32
|
+
#: at module scope and this package installs with no compiled extension at all -- and they are
|
|
33
|
+
#: Studio's now, so importing them is not a choice this repository has. The spellings are held
|
|
34
|
+
#: against each other by ``tests/test_thin_user_agent.py`` under
|
|
35
|
+
#: ``make verify-cross-repo STUDIO_ROOT=...``, which reads the other one out of Studio's source.
|
|
36
|
+
CRAWLER_CONTACT_SEGMENT = "(+https://mostlyright.md/crawler; crawler@mostlyright.md)"
|
|
37
|
+
|
|
38
|
+
#: The distribution this client is installed as, and the product token it therefore reports.
|
|
39
|
+
DISTRIBUTION_NAME = "mostlyright-data"
|
|
40
|
+
|
|
41
|
+
#: The version reported from a source tree, where no distribution metadata exists to read. A
|
|
42
|
+
#: number rather than a word, so every value this header can carry parses the same way.
|
|
43
|
+
UNINSTALLED_VERSION = "0.0.0"
|
|
44
|
+
|
|
45
|
+
_USER_AGENT_HEADER = "User-Agent"
|
|
46
|
+
|
|
47
|
+
#: The header naming the agent driving this process, one of :data:`AGENTS`.
|
|
48
|
+
AGENT_HEADER = "X-Mostlyright-Agent"
|
|
49
|
+
|
|
50
|
+
#: The header carrying :func:`invocation_id`, sent only after :func:`begin_invocation`.
|
|
51
|
+
INVOCATION_HEADER = "X-Mostlyright-Invocation"
|
|
52
|
+
|
|
53
|
+
#: Set to anything but ``0`` or empty, and neither header above is sent.
|
|
54
|
+
NO_AGENT_HEADERS_ENV = "MOSTLYRIGHT_NO_AGENT_HEADERS"
|
|
55
|
+
|
|
56
|
+
AGENT_CLAUDE_CODE = "claude-code"
|
|
57
|
+
AGENT_CODEX = "codex"
|
|
58
|
+
AGENT_GEMINI_CLI = "gemini-cli"
|
|
59
|
+
AGENT_CURSOR = "cursor"
|
|
60
|
+
AGENT_CI = "ci"
|
|
61
|
+
AGENT_NONE = "none"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AgentMarker(NamedTuple):
|
|
65
|
+
"""One variable an agent host exports into the commands it runs.
|
|
66
|
+
|
|
67
|
+
``conversation`` says whether the attendance check (``ux.attendance``) reads the variable as
|
|
68
|
+
a conversation existing. Only a marker every headless run of that host lacks may say so:
|
|
69
|
+
``CODEX_SANDBOX`` is set by ``codex exec`` on a cron job too, so it names Codex here and is
|
|
70
|
+
not read as a person being there.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
agent: str
|
|
74
|
+
variable: str
|
|
75
|
+
conversation: bool
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#: Every marker, grouped by agent in precedence order.
|
|
79
|
+
#:
|
|
80
|
+
#: Presence of a non-empty value is the signal and the value is never read: these are the hosts'
|
|
81
|
+
#: variables, and their values are theirs to change. A coding agent outranks ``ci``, so
|
|
82
|
+
#: ``claude -p`` inside a GitHub Action reports ``claude-code``. Nothing that every terminal of an
|
|
83
|
+
#: editor exports is listed, because a person typing there would read as the agent.
|
|
84
|
+
AGENT_MARKERS: tuple[AgentMarker, ...] = (
|
|
85
|
+
AgentMarker(AGENT_CLAUDE_CODE, "CLAUDECODE", True),
|
|
86
|
+
AgentMarker(AGENT_CLAUDE_CODE, "CLAUDE_CODE_SESSION", True),
|
|
87
|
+
AgentMarker(AGENT_CODEX, "CODEX_SESSION", True),
|
|
88
|
+
AgentMarker(AGENT_CODEX, "CODEX_SANDBOX", False),
|
|
89
|
+
AgentMarker(AGENT_CODEX, "CODEX_SANDBOX_NETWORK_DISABLED", False),
|
|
90
|
+
AgentMarker(AGENT_GEMINI_CLI, "GEMINI_CLI", False),
|
|
91
|
+
AgentMarker(AGENT_CURSOR, "CURSOR_AGENT", False),
|
|
92
|
+
AgentMarker(AGENT_CI, "CI", False),
|
|
93
|
+
AgentMarker(AGENT_CI, "GITHUB_ACTIONS", False),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
#: Every value :data:`AGENT_HEADER` can carry.
|
|
97
|
+
AGENTS: tuple[str, ...] = (*dict.fromkeys(marker.agent for marker in AGENT_MARKERS), AGENT_NONE)
|
|
98
|
+
|
|
99
|
+
#: What :func:`invocation_id` returns: 16 random bytes as 32 lowercase hex digits.
|
|
100
|
+
INVOCATION_ID_PATTERN = r"^[0-9a-f]{32}$"
|
|
101
|
+
|
|
102
|
+
_invocation: str | None = None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@cache
|
|
106
|
+
def client_user_agent() -> str:
|
|
107
|
+
"""Return the ``User-Agent`` this client presents to Mostly Right.
|
|
108
|
+
|
|
109
|
+
Cached because the answer is a property of the installed environment, and reading distribution
|
|
110
|
+
metadata walks ``sys.path``; a command that makes twenty calls would otherwise pay for it
|
|
111
|
+
twenty times.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
installed = version(DISTRIBUTION_NAME)
|
|
116
|
+
except PackageNotFoundError:
|
|
117
|
+
installed = UNINSTALLED_VERSION
|
|
118
|
+
return f"{DISTRIBUTION_NAME}/{installed} {CRAWLER_CONTACT_SEGMENT}"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def with_user_agent(headers: Mapping[str, str]) -> dict[str, str]:
|
|
122
|
+
"""Return ``headers`` as a new dict carrying this client's identity.
|
|
123
|
+
|
|
124
|
+
A caller that stated its own ``User-Agent`` keeps it, under whatever spelling it used: HTTP
|
|
125
|
+
header names are case-insensitive, so adding a second one under a different capitalization
|
|
126
|
+
would send two identities rather than override one.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
resolved = dict(headers)
|
|
130
|
+
if any(name.lower() == "user-agent" for name in resolved):
|
|
131
|
+
return resolved
|
|
132
|
+
resolved[_USER_AGENT_HEADER] = client_user_agent()
|
|
133
|
+
return resolved
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def agent_env(agent: str) -> tuple[str, ...]:
|
|
137
|
+
"""Return the variables that name ``agent``; empty for ``none`` and for a name not listed."""
|
|
138
|
+
|
|
139
|
+
return tuple(marker.variable for marker in AGENT_MARKERS if marker.agent == agent)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def conversation_env() -> tuple[str, ...]:
|
|
143
|
+
"""Return the markers the attendance check reads as a conversation existing."""
|
|
144
|
+
|
|
145
|
+
return tuple(marker.variable for marker in AGENT_MARKERS if marker.conversation)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def first_set(names: tuple[str, ...], environ: Mapping[str, str] | None = None) -> str | None:
|
|
149
|
+
"""Return the first of ``names`` set to a non-empty value, or ``None``."""
|
|
150
|
+
|
|
151
|
+
table = os.environ if environ is None else environ
|
|
152
|
+
return next((name for name in names if (table.get(name) or "").strip()), None)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def detect_agent(environ: Mapping[str, str] | None = None) -> str:
|
|
156
|
+
"""Return the agent driving this process, one of :data:`AGENTS`."""
|
|
157
|
+
|
|
158
|
+
table = os.environ if environ is None else environ
|
|
159
|
+
for marker in AGENT_MARKERS:
|
|
160
|
+
if (table.get(marker.variable) or "").strip():
|
|
161
|
+
return marker.agent
|
|
162
|
+
return AGENT_NONE
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def begin_invocation() -> str:
|
|
166
|
+
"""Draw a fresh invocation id for the command starting now, and return it.
|
|
167
|
+
|
|
168
|
+
Called by the ``mr-data`` entry point once per command. A program importing this package
|
|
169
|
+
never calls it, so its requests carry no invocation id rather than one that lasts as long as
|
|
170
|
+
the process does.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
global _invocation
|
|
174
|
+
_invocation = secrets.token_hex(16)
|
|
175
|
+
return _invocation
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def end_invocation() -> None:
|
|
179
|
+
"""Forget the current invocation id, so later requests carry none."""
|
|
180
|
+
|
|
181
|
+
global _invocation
|
|
182
|
+
_invocation = None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def invocation_id() -> str | None:
|
|
186
|
+
"""Return the current command's id (see :data:`INVOCATION_ID_PATTERN`), or ``None``."""
|
|
187
|
+
|
|
188
|
+
return _invocation
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def agent_headers(environ: Mapping[str, str] | None = None) -> dict[str, str]:
|
|
192
|
+
"""Return the agent and invocation headers, or none when :data:`NO_AGENT_HEADERS_ENV` is set.
|
|
193
|
+
|
|
194
|
+
The invocation header is present only inside a command :func:`begin_invocation` started. A
|
|
195
|
+
first-party call adds these itself; the transport never does.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
table = os.environ if environ is None else environ
|
|
199
|
+
if (table.get(NO_AGENT_HEADERS_ENV) or "").strip() not in ("", "0"):
|
|
200
|
+
return {}
|
|
201
|
+
headers = {AGENT_HEADER: detect_agent(table)}
|
|
202
|
+
if _invocation is not None:
|
|
203
|
+
headers[INVOCATION_HEADER] = _invocation
|
|
204
|
+
return headers
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
__all__ = [
|
|
208
|
+
"AGENTS",
|
|
209
|
+
"AGENT_CI",
|
|
210
|
+
"AGENT_CLAUDE_CODE",
|
|
211
|
+
"AGENT_CODEX",
|
|
212
|
+
"AGENT_CURSOR",
|
|
213
|
+
"AGENT_GEMINI_CLI",
|
|
214
|
+
"AGENT_HEADER",
|
|
215
|
+
"AGENT_MARKERS",
|
|
216
|
+
"AGENT_NONE",
|
|
217
|
+
"CRAWLER_CONTACT_SEGMENT",
|
|
218
|
+
"DISTRIBUTION_NAME",
|
|
219
|
+
"INVOCATION_HEADER",
|
|
220
|
+
"INVOCATION_ID_PATTERN",
|
|
221
|
+
"NO_AGENT_HEADERS_ENV",
|
|
222
|
+
"UNINSTALLED_VERSION",
|
|
223
|
+
"AgentMarker",
|
|
224
|
+
"agent_env",
|
|
225
|
+
"agent_headers",
|
|
226
|
+
"begin_invocation",
|
|
227
|
+
"client_user_agent",
|
|
228
|
+
"conversation_env",
|
|
229
|
+
"detect_agent",
|
|
230
|
+
"end_invocation",
|
|
231
|
+
"first_set",
|
|
232
|
+
"invocation_id",
|
|
233
|
+
"with_user_agent",
|
|
234
|
+
]
|
{mostlyright_data-0.31.12 → mostlyright_data-0.31.14}/src/mostlyright/data_harness/thin/v4.py
RENAMED
|
@@ -149,8 +149,8 @@ from mostlyright.data_harness.thin.v4_matching import CONTRACT_PATHS as MATCHING
|
|
|
149
149
|
#: ``JOB_INVALID`` outright, which in production was every refresh a collection epoch was offered
|
|
150
150
|
#: for rather than only the collection runs. This package must not reach a Studio older than the
|
|
151
151
|
#: commit it pins; ``docs/V4-WORKER-PROTOCOL.md`` states it beside the layout's own ordering rule.
|
|
152
|
-
PINNED_V4_OPENAPI_SOURCE_SHA256 = "
|
|
153
|
-
PINNED_V4_CONTRACT_VERSION = "4.
|
|
152
|
+
PINNED_V4_OPENAPI_SOURCE_SHA256 = "b378d4f70d580aed094b00edeec5acc39cae5c79f19dc4e869fe40304fccf654"
|
|
153
|
+
PINNED_V4_CONTRACT_VERSION = "4.57.0"
|
|
154
154
|
|
|
155
155
|
#: The ``schema_version`` every command body that predates 4.14.0 stamps: ``dataset activity``,
|
|
156
156
|
#: ``table rename``, ``table archive`` and ``dataset archive``. It is the number the 0.25 line
|
|
@@ -136,6 +136,7 @@ from mostlyright.data_harness.thin.session import (
|
|
|
136
136
|
CLOUD_REFUSAL_CODE,
|
|
137
137
|
MAX_JSON_RESPONSE_BYTES,
|
|
138
138
|
MAX_STATED_MESSAGE_CHARS,
|
|
139
|
+
THIN_SANDBOX_KEY_READ_ONLY,
|
|
139
140
|
CloudClient,
|
|
140
141
|
StudioSession,
|
|
141
142
|
StudioSessionProvider,
|
|
@@ -1615,18 +1616,30 @@ def _sync_written_identity(dataset_id: str, cloud_url: str) -> dict[str, Any]:
|
|
|
1615
1616
|
)
|
|
1616
1617
|
return {"public_projection_synced": True}
|
|
1617
1618
|
except (ThinLaneError, LoginError, OSError) as error:
|
|
1618
|
-
|
|
1619
|
+
stated = isinstance(error, ThinLaneError) and getattr(error, "http_status", None) != 404
|
|
1620
|
+
result: dict[str, Any] = {
|
|
1619
1621
|
"public_projection_synced": False,
|
|
1620
|
-
"public_projection_error": (
|
|
1621
|
-
error.code
|
|
1622
|
-
if isinstance(error, ThinLaneError) and getattr(error, "http_status", None) != 404
|
|
1623
|
-
else "THIN_IDENTITY_SYNC_UNAVAILABLE"
|
|
1624
|
-
),
|
|
1622
|
+
"public_projection_error": (error.code if stated else "THIN_IDENTITY_SYNC_UNAVAILABLE"),
|
|
1625
1623
|
"public_projection_note": (
|
|
1626
1624
|
"The dataset was saved, but its public page could not be updated. "
|
|
1627
1625
|
f"Retry only the sync with: mr-data dataset sync {dataset_id}"
|
|
1628
1626
|
),
|
|
1629
1627
|
}
|
|
1628
|
+
# ⚠ THE CODE ALONE SENT PEOPLE TO THE WRONG FIX. Every refusal on this path left
|
|
1629
|
+
# as a bare code, so a key Cloud would never let update the page and an expired one
|
|
1630
|
+
# read the same. The refusal's own sentence -- this lane's words, with Cloud's
|
|
1631
|
+
# quoted inside them already bounded and printable by ``cloud_refusal`` -- rides
|
|
1632
|
+
# along. Nothing branches on it; the code beside it is what a script reads.
|
|
1633
|
+
if stated:
|
|
1634
|
+
result["public_projection_message"] = error.detail
|
|
1635
|
+
if stated and error.code == THIN_SANDBOX_KEY_READ_ONLY:
|
|
1636
|
+
# Retrying with this key is refused the same way: the chat does it.
|
|
1637
|
+
result["public_projection_note"] = (
|
|
1638
|
+
"The dataset was saved, but this web chat key cannot update its public "
|
|
1639
|
+
"page. Use the chat's update_dataset tool with sync_only: true, or a "
|
|
1640
|
+
"person syncs it from the dashboard."
|
|
1641
|
+
)
|
|
1642
|
+
return result
|
|
1630
1643
|
|
|
1631
1644
|
|
|
1632
1645
|
def _held(record: Mapping[str, Any], member: str) -> Any:
|