mostlyright-data 0.31.9__tar.gz → 0.31.11__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.9 → mostlyright_data-0.31.11}/PKG-INFO +3 -2
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/README.md +2 -1
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/scripts/hatch_build.py +1 -1
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/making-a-table-incremental.md +12 -4
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/preparing-table-search.md +27 -8
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/readers.md +3 -2
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/recipe-test.md +4 -3
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/source-credentials.md +2 -1
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/sources.md +72 -5
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/activity.py +6 -5
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/recipe.py +10 -6
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/recipe_explain.py +94 -20
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/recipe_lint.py +90 -14
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/recipe_test.py +46 -11
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/runs.py +96 -7
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/session.py +83 -29
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/studio_capabilities.py +8 -1
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/table_search_contract.json +87 -18
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4.py +2 -2
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_search.py +31 -1
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/.gitignore +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/pyproject.toml +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/SKILL.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/agents/openai.yaml +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/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.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/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.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/agent-protocol.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/boundaries.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/commands.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/enriching-with-small-models.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/installation-parity.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/live-run.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/narrating-the-run.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/one-install.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/patterns.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/prediction-labels.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/promote.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/receipts.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/reference-pages.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/required-protocol.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/transforms.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/user-communication-contract.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/__init__.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/agent_protocol.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/canonical.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/formats.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/key_seam.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/page_coverage.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/session_probes.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/skill_assets.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/table_manifest.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/__init__.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/acquire.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/approvals.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/backoff.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/categories.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/commands.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/delivery.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/description_shape.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/download.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/enrichment.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/matching_benchmark.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/narrative.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/parity.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/probe.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/propose.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/release_check.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/research.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/router.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/stream.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/support.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/table_view.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/transport.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_matching.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/__init__.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/attendance.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/clarification.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/credentials.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/login.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/ux/remediation.py +0 -0
- {mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/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.11
|
|
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/
|
|
@@ -216,10 +216,11 @@ There is no `dev` extra to ask for -- `uv run --extra dev` answers
|
|
|
216
216
|
tooling is the default dependency group, which `uv sync` installs.
|
|
217
217
|
|
|
218
218
|
`make lint` is the repository's one lint set (`uv run ruff check .` plus `uv lock --check`); it is
|
|
219
|
-
the first thing `make verify` runs and the same target the
|
|
219
|
+
the first thing `make verify` runs and the same target the checkout leg of `thin-client-smoke.yml`
|
|
220
220
|
runs, so the two cannot disagree about what is linted.
|
|
221
221
|
|
|
222
222
|
The test suite checks client behavior, contract compatibility and thin package ownership. Backend
|
|
223
223
|
execution and worker-boundary tests run in Studio. The client supports Linux, macOS and Windows.
|
|
224
|
+
CI runs on Linux only, on the AWS runner pool, so macOS and Windows are not tested there.
|
|
224
225
|
|
|
225
226
|
To update the public Studio contract pin, run `make repin STUDIO_GIT=/path/to/studio STUDIO_REF=<commit>`. It reads committed OpenAPI and JSON Schema bytes, records their hashes and source commit in `vendor/studio-contracts/client-v4-pin.json`, and updates the thin client. Run the cross-repository tests against that Studio revision before release. Worker/client generation and backend job-contract repins now belong to Studio.
|
|
@@ -201,10 +201,11 @@ There is no `dev` extra to ask for -- `uv run --extra dev` answers
|
|
|
201
201
|
tooling is the default dependency group, which `uv sync` installs.
|
|
202
202
|
|
|
203
203
|
`make lint` is the repository's one lint set (`uv run ruff check .` plus `uv lock --check`); it is
|
|
204
|
-
the first thing `make verify` runs and the same target the
|
|
204
|
+
the first thing `make verify` runs and the same target the checkout leg of `thin-client-smoke.yml`
|
|
205
205
|
runs, so the two cannot disagree about what is linted.
|
|
206
206
|
|
|
207
207
|
The test suite checks client behavior, contract compatibility and thin package ownership. Backend
|
|
208
208
|
execution and worker-boundary tests run in Studio. The client supports Linux, macOS and Windows.
|
|
209
|
+
CI runs on Linux only, on the AWS runner pool, so macOS and Windows are not tested there.
|
|
209
210
|
|
|
210
211
|
To update the public Studio contract pin, run `make repin STUDIO_GIT=/path/to/studio STUDIO_REF=<commit>`. It reads committed OpenAPI and JSON Schema bytes, records their hashes and source commit in `vendor/studio-contracts/client-v4-pin.json`, and updates the thin client. Run the cross-repository tests against that Studio revision before release. Worker/client generation and backend job-contract repins now belong to Studio.
|
|
@@ -15,7 +15,7 @@ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
|
15
15
|
#
|
|
16
16
|
# A compiled member would not, which is why the tag is not the only thing this hook does. It also
|
|
17
17
|
# REFUSES TO BUILD when it finds one, rather than falling back to a platform tag: a fallback is
|
|
18
|
-
# the worst outcome on the list, because the
|
|
18
|
+
# the worst outcome on the list, because the thin-client smoke would keep passing while quietly
|
|
19
19
|
# testing a different artifact. A universal tag over a compiled payload is worse still -- it
|
|
20
20
|
# installs everywhere and fails at import on all but one platform, on somebody else's machine.
|
|
21
21
|
#
|
|
@@ -603,16 +603,24 @@ again. Ask the user before passing either flag. Never pass one to clear a refusa
|
|
|
603
603
|
|
|
604
604
|
Check `studio_capabilities.keyless_window` is literal `true` before using this grammar.
|
|
605
605
|
An absent or false capability means "not available on this Studio yet". Registration and
|
|
606
|
-
file explanation refuse with `THIN_GRAMMAR_NOT_SERVED` before local lint or registration
|
|
607
|
-
|
|
606
|
+
file explanation refuse with `THIN_GRAMMAR_NOT_SERVED` before local lint or registration.
|
|
607
|
+
An older API may answer the explanation with a schema 422 before it reports capabilities. The
|
|
608
|
+
client then runs its local checks and lint. A local fault is reported as itself. Only a clean
|
|
609
|
+
document is refused as not served. That refusal marks each member with the capability it needs,
|
|
610
|
+
`keyless_post` members first. No dataset delegation is written.
|
|
608
611
|
The capability covers `public.https.api@1.0.0`, `generic_api.auth.kind: none`, endpoint
|
|
609
612
|
`epoch_millis`, the `epoch_millis_value` partition key, and the two request controls below. Existing credential-free file and collection
|
|
610
613
|
adapters do not require this capability.
|
|
611
614
|
|
|
612
615
|
For a public JSON/CSV API use `credential_mode: none`, an HTTPS `origin`, and `generic_api`
|
|
613
|
-
with exact `auth: {"kind":"none"}`.
|
|
616
|
+
with exact `auth: {"kind":"none"}`. A request is a GET, or a POST with a `body_template` on
|
|
617
|
+
Studio 4.49. It states no credential reference. A POST needs `studio_capabilities.keyless_post`
|
|
618
|
+
literal `true`. Otherwise registration refuses with `THIN_GRAMMAR_NOT_SERVED` and names the
|
|
619
|
+
request `method` first, marked `(keyless_post)`. Send a GET in its place, or wait for the Studio
|
|
620
|
+
deploy.
|
|
614
621
|
Authenticated adapters retain their existing credential requirements. Declare both window
|
|
615
|
-
endpoints with `location: parameter` and corresponding required request parameters.
|
|
622
|
+
endpoints with `location: parameter` and corresponding required request parameters. A
|
|
623
|
+
parameter fills its placeholder in the query, or in the body of a POST. Origin,
|
|
616
624
|
host allowlists and cursor next-URL restrictions still apply.
|
|
617
625
|
|
|
618
626
|
For Binance BTCUSDT one-minute klines, start the backfill at `2017-08-17T00:00:00Z` and request
|
|
@@ -146,7 +146,10 @@ column description.
|
|
|
146
146
|
|
|
147
147
|
## 6. Write the vocabulary column by column
|
|
148
148
|
|
|
149
|
-
An alias maps a phrase to an exact filter, and a sort alias maps a phrase to a sort.
|
|
149
|
+
An alias maps a phrase to an exact filter, and a sort alias maps a phrase to a sort. Once
|
|
150
|
+
planning#1842 lands, the live page's search node applies the aliases and merges them with the
|
|
151
|
+
planner's plan. Until then aliases are honoured only by `search explain`, and only while the
|
|
152
|
+
planner is unavailable for that table. The live page applies none. List every
|
|
150
153
|
declared column before you write the spec. Then generate its phrases from its type:
|
|
151
154
|
|
|
152
155
|
| Column | Phrases | Spec entry |
|
|
@@ -200,12 +203,27 @@ nothing guarantees that.
|
|
|
200
203
|
## 8. Turn the planner on
|
|
201
204
|
|
|
202
205
|
The planner turns a sentence into filters, sorts and preferences. Turn it on for every table with
|
|
203
|
-
search
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
search with `"enabled": true`. A query whose every word an alias resolved skips it. Queries with
|
|
207
|
+
negation or "or" stay unresolved rather than becoming a guessed filter.
|
|
208
|
+
|
|
209
|
+
The planner model sees the query text, the column names and their descriptions, value
|
|
210
|
+
dictionaries of at most 16 columns of at most 256 short values each, the aliases and the field
|
|
211
|
+
roles. It never sees rows.
|
|
212
|
+
|
|
213
|
+
`external_context_allowed` is the owner's consent to send that context to the planner model:
|
|
214
|
+
|
|
215
|
+
- A public table whose sources are all public gets the planner on its live page whatever the flag
|
|
216
|
+
says. Set the flag to `true` on it, so `search explain` and `capabilities.planner` match the page.
|
|
217
|
+
- A private or link-shared table gets the planner only when the recipe sets the flag to `true`.
|
|
218
|
+
That is a deliberate yes from the user or the workspace policy. Data-read permission is not that
|
|
219
|
+
consent. Without that yes, keep the flag `false`.
|
|
220
|
+
- Until the Studio change in planning#1844 is live, the flag changes only `search explain` and the
|
|
221
|
+
`capabilities.planner` readout. The live page plans a public table with public sources and no
|
|
222
|
+
other table.
|
|
223
|
+
|
|
224
|
+
`search inspect`, `search status` and `search explain` say why the planner is unavailable in
|
|
225
|
+
`planner_detail.reason`. `external_context_not_allowed` means the recipe withholds consent.
|
|
226
|
+
`private_source` means a source is not public.
|
|
209
227
|
|
|
210
228
|
Write each column description as if the planner reads it. Say what the column holds, its unit,
|
|
211
229
|
and the words people use for it.
|
|
@@ -314,7 +332,8 @@ mr-data search explain TABLE_ID --version VERSION_ID --query "stocks under 10" -
|
|
|
314
332
|
|
|
315
333
|
Read `planner_used`, `filters` and `order_by`. A query that asks for a filter or a sort and plans
|
|
316
334
|
text only is a gap in the spec. Add the alias or sharpen the column description, then check again.
|
|
317
|
-
`planner_used: false` on a settled query is a fault to report, unless aliases resolved every word
|
|
335
|
+
`planner_used: false` on a settled query is a fault to report, unless aliases resolved every word
|
|
336
|
+
or `planner_detail.reason` says the planner is withheld.
|
|
318
337
|
|
|
319
338
|
Run real queries against the live search once `package_state` is `ready`:
|
|
320
339
|
|
{mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/readers.md
RENAMED
|
@@ -173,8 +173,9 @@ response matches the family's exact media-type, settings, and budget contract.
|
|
|
173
173
|
### Needs Studio 4.26
|
|
174
174
|
|
|
175
175
|
Check first: run `mr-data recipe explain RECIPE.json --json` and read `studio_capabilities`.
|
|
176
|
-
Use `public.https.api@1.0.0` only when `keyless_window` is literal `true
|
|
177
|
-
"not available on this Studio yet".
|
|
176
|
+
Use `public.https.api@1.0.0` only when `keyless_window` is literal `true`. Otherwise report
|
|
177
|
+
"not available on this Studio yet". A POST request needs Studio 4.49 and `keyless_post` literal
|
|
178
|
+
`true` as well. Registration and file explanation refuse with
|
|
178
179
|
`THIN_GRAMMAR_NOT_SERVED`, including an older API's schema 422 before capabilities.
|
|
179
180
|
|
|
180
181
|
The new adapter uses the existing `generic_api.response` Reader contract. For Binance klines,
|
{mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/recipe-test.md
RENAMED
|
@@ -43,7 +43,7 @@ Read `status`:
|
|
|
43
43
|
| `recipe_test_cancelled` | Cancelled before it reported. | 2 |
|
|
44
44
|
| `recipe_test_awaiting_confirmation` | Held for a size confirmation. Run `confirm_command`, then ask again. The hold is a size check, not a charge. | 2 |
|
|
45
45
|
| `recipe_test_started` | `--no-wait` returned before the report. | 0 |
|
|
46
|
-
| `recipe_test_still_running` | Not reported within the wait. Ask again with the same bounds. | 2 |
|
|
46
|
+
| `recipe_test_still_running` | Not reported within the wait. `test_status: queued` means no worker holds the test yet, so nothing is wrong with the recipe. Ask again with the same bounds. | 2 |
|
|
47
47
|
| `recipe_test_skipped` | Nothing was tested. `test.reason` says why: `not_served` (Studio predates the test), `engine_unsupported` (the recipe runs on the Keel engine) or `worker_unsupported` (the worker predates the test and refused it `JOB_INVALID`). | 0 |
|
|
48
48
|
|
|
49
49
|
Asking again with the same bounds reads a running test back, and a completed test replays its
|
|
@@ -66,8 +66,9 @@ page is fetched twice, and the receipt's `recipe_test` block names the test run.
|
|
|
66
66
|
When the test finds a blocking problem, fails, is held or is still running after those five
|
|
67
67
|
minutes, the full is not started. The status is `run_stopped_by_test`,
|
|
68
68
|
`run_test_awaiting_confirmation` or `run_test_still_running`, `run_id` names the test, and the
|
|
69
|
-
command exits 2.
|
|
70
|
-
|
|
69
|
+
command exits 2. `test.test_status: queued` on `run_test_still_running` means no worker had
|
|
70
|
+
picked the test up; `mr-data status RUN_ID` says when one has. Running the same command again
|
|
71
|
+
picks a running test back up, replays a finished one, and tests again after a failure. `next` ends with the command that starts the
|
|
71
72
|
same full without the test. Repair the recipe first. Skip the test only when the user decides the
|
|
72
73
|
problem does not matter:
|
|
73
74
|
|
|
@@ -33,7 +33,8 @@ the user — the source needs a credential and which one — and nothing about t
|
|
|
33
33
|
A [per-row request](sources.md#authoring-a-per-row-request) presents a key by its source's
|
|
34
34
|
`connector.credential_mode`.
|
|
35
35
|
|
|
36
|
-
- `none` presents nothing
|
|
36
|
+
- `none` presents nothing. The request is a `GET`. Needs Studio 4.49: it may be a `POST` with a
|
|
37
|
+
`body_template` when `studio_capabilities.keyless_post` is literal `true`.
|
|
37
38
|
- `opaque_reference` presents the secret named in `credential.secret_name`, the way
|
|
38
39
|
`request.auth` says. A saved connection cannot stand in for it.
|
|
39
40
|
- `platform` presents a key Mostly Right holds, so the user enrols nothing.
|
{mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/skills/mr-data-build/references/sources.md
RENAMED
|
@@ -254,10 +254,15 @@ breaks a rule above.
|
|
|
254
254
|
|
|
255
255
|
| `credential_mode` | State | Use it for |
|
|
256
256
|
| --- | --- | --- |
|
|
257
|
-
| `none` | no `credential
|
|
257
|
+
| `none` | no `credential` and no `auth`. A `GET`, or a `POST` with `body_template` (Needs Studio 4.49) | a public API that takes no key |
|
|
258
258
|
| `opaque_reference` | `credential.secret_name` and `request.auth` | a key the user enrolled with `mr-data keys set` |
|
|
259
259
|
| `platform` | no `credential` and no `auth` | an endpoint Mostly Right holds a key for |
|
|
260
260
|
|
|
261
|
+
A keyless per-row `POST` needs Studio 4.49. Use it only when `keyless_post` is literal `true` in
|
|
262
|
+
`studio_capabilities`. Otherwise the client refuses with `THIN_GRAMMAR_NOT_SERVED` and names the
|
|
263
|
+
request `method` first, marked `(keyless_post)`. Send a GET in its place, or wait for the Studio
|
|
264
|
+
deploy.
|
|
265
|
+
|
|
261
266
|
The platform credential needs no key from anyone, and any subscribed workspace may use it. Each
|
|
262
267
|
binding is one route and one pinned model, and `mr-data models --json` lists the ones this
|
|
263
268
|
deployment serves: Clef for classification (`classify`), TypeSafe's Jev through OpenRouter
|
|
@@ -378,18 +383,80 @@ full grammar.
|
|
|
378
383
|
### Needs Studio 4.26
|
|
379
384
|
|
|
380
385
|
Check first: run `mr-data recipe explain RECIPE.json --json` and read `studio_capabilities`.
|
|
381
|
-
Use the following only when `keyless_window` is literal `true
|
|
382
|
-
on this Studio yet". The client refuses with `THIN_GRAMMAR_NOT_SERVED` before registration
|
|
383
|
-
|
|
386
|
+
Use the following only when `keyless_window` is literal `true`. Otherwise report "not available
|
|
387
|
+
on this Studio yet". The client refuses with `THIN_GRAMMAR_NOT_SERVED` before registration.
|
|
388
|
+
An older API may reject the explanation document with a schema 422 before it reports
|
|
389
|
+
capabilities. The client then runs its local checks and lint. A local fault is reported as
|
|
390
|
+
itself. Only a clean document is refused as not served. That refusal marks each member with the
|
|
391
|
+
capability it needs, `keyless_post` members first.
|
|
384
392
|
|
|
385
393
|
`public.https.api@1.0.0` is the credential-free generic HTTPS API adapter. State
|
|
386
394
|
`credential_mode: none`, `origin`, and `generic_api` with exact `auth: {"kind":"none"}`.
|
|
387
|
-
|
|
395
|
+
A request is a GET, or a POST with a `body_template` on Studio 4.49. It presents no credential:
|
|
396
|
+
no credential reference, token, secret header or secret query value.
|
|
388
397
|
This adapter does not relax authentication on the keyed adapters. The owned HTTPS origin,
|
|
389
398
|
host allowlist, bounded request budgets and cursor next-URL restrictions remain in force.
|
|
390
399
|
The existing generic response mapping and JSON/CSV pagination apply; a cursor needs the
|
|
391
400
|
publisher's actual next-token pointer. JSON-pointer pagination does not extract XML OAI-PMH tokens.
|
|
392
401
|
|
|
402
|
+
Needs Studio 4.49. Some APIs take the date range in a JSON body. Send a POST and state the body
|
|
403
|
+
in `body_template`. Use it only when `keyless_post` is literal `true` in `studio_capabilities`.
|
|
404
|
+
Otherwise the client refuses with `THIN_GRAMMAR_NOT_SERVED` and names
|
|
405
|
+
`generic_api/request/method` first, marked `(keyless_post)`. Send a GET in its place, or wait for
|
|
406
|
+
the Studio deploy.
|
|
407
|
+
Declare both window endpoints with `location: parameter`. Each endpoint fills the body
|
|
408
|
+
placeholder of the parameter it names. One request covers one window unit, so state
|
|
409
|
+
`pagination: {"kind": "none", "max_pages": 1}`. The values frozen in `connector.parameters` are
|
|
410
|
+
the first day of the window, the day `start_at` names. This response is a root array, so
|
|
411
|
+
`records_pointer` is `""`. Write `response.reader` the way [Readers](readers.md#needs-studio-426)
|
|
412
|
+
shows. The publisher here is invented:
|
|
413
|
+
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"connector": {
|
|
417
|
+
"adapter_id": "public.https.api@1.0.0",
|
|
418
|
+
"credential_mode": "none",
|
|
419
|
+
"origin": "https://api.prices.example",
|
|
420
|
+
"parameters": [
|
|
421
|
+
{"name": "from_date", "value": "2026-01-01"},
|
|
422
|
+
{"name": "to_date", "value": "2026-01-01"}
|
|
423
|
+
],
|
|
424
|
+
"generic_api": {
|
|
425
|
+
"request": {
|
|
426
|
+
"method": "POST",
|
|
427
|
+
"path_template": "/v1/prices/query",
|
|
428
|
+
"static_query": [],
|
|
429
|
+
"static_headers": [],
|
|
430
|
+
"body_template": {"market": "day_ahead", "from": "{from_date}", "to": "{to_date}"},
|
|
431
|
+
"parameters": [
|
|
432
|
+
{"name": "from_date", "type": "string", "required": true},
|
|
433
|
+
{"name": "to_date", "type": "string", "required": true}
|
|
434
|
+
]
|
|
435
|
+
},
|
|
436
|
+
"auth": {"kind": "none"},
|
|
437
|
+
"response": {"format": "json", "records_pointer": ""},
|
|
438
|
+
"pagination": {"kind": "none", "max_pages": 1}
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
"window": {
|
|
442
|
+
"start_at": "2026-01-01T00:00:00Z",
|
|
443
|
+
"granularity": "day",
|
|
444
|
+
"timezone": "UTC",
|
|
445
|
+
"lookback_seconds": 86400,
|
|
446
|
+
"max_span_seconds": 31622400,
|
|
447
|
+
"request": {
|
|
448
|
+
"start": {"encoding": "iso_date", "bound": "inclusive", "location": "parameter", "parameter": "from_date"},
|
|
449
|
+
"end": {"encoding": "iso_date", "bound": "inclusive", "location": "parameter", "parameter": "to_date"}
|
|
450
|
+
},
|
|
451
|
+
"merge": {
|
|
452
|
+
"materialization": "partition_replace",
|
|
453
|
+
"partition": {"column": "date", "key": "iso_date_value"},
|
|
454
|
+
"row_identity": ["date", "hour"]
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
393
460
|
For Binance klines use parameter-windowed `startTime`/`endTime`, millisecond endpoints and
|
|
394
461
|
12-hour slices at one request per second. Follow the backfill and hourly-refresh example in
|
|
395
462
|
[making a table incremental](making-a-table-incremental.md#keyless-api-windows-studio-426)
|
{mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/activity.py
RENAMED
|
@@ -50,7 +50,7 @@ from time import monotonic
|
|
|
50
50
|
from typing import Any
|
|
51
51
|
|
|
52
52
|
from mostlyright.data_harness.thin import THIN_SCHEMA_PREFIX
|
|
53
|
-
from mostlyright.data_harness.thin.session import
|
|
53
|
+
from mostlyright.data_harness.thin.session import StudioSessionProvider, dataset_dashboard_url
|
|
54
54
|
from mostlyright.data_harness.thin.transport import (
|
|
55
55
|
REQUEST_TIMEOUT_SECONDS,
|
|
56
56
|
ThinLaneError,
|
|
@@ -931,8 +931,9 @@ def _document_counts(document: Mapping[str, Any]) -> tuple[int, int]:
|
|
|
931
931
|
#: One session per process, because two reports in one command -- ``recipe`` says one thing before
|
|
932
932
|
#: it registers and another after -- must not buy two token exchanges. It is never shared with a
|
|
933
933
|
#: command's own client: those open their own, and a cache reaching across the two would make a
|
|
934
|
-
#: progress report able to break a command by holding an expired token.
|
|
935
|
-
|
|
934
|
+
#: progress report able to break a command by holding an expired token. It is a provider, so the
|
|
935
|
+
#: clients built on it share one renewal when the token nears its expiry rather than one each.
|
|
936
|
+
_SESSION: StudioSessionProvider | None = None
|
|
936
937
|
|
|
937
938
|
#: The verb's transport, which is cached because its clock is a constant. The automatic half's is
|
|
938
939
|
#: not cached and cannot be: its clock is a deadline, and a deadline belongs to one report.
|
|
@@ -993,11 +994,11 @@ def _reporting_client(budget: _Budget) -> StudioV4ActivityClient:
|
|
|
993
994
|
|
|
994
995
|
def _client_on(transport: Transport) -> StudioV4ActivityClient:
|
|
995
996
|
global _SESSION
|
|
996
|
-
from mostlyright.data_harness.thin.session import
|
|
997
|
+
from mostlyright.data_harness.thin.session import open_renewable_session
|
|
997
998
|
from mostlyright.data_harness.ux.credentials import resolve_cloud_credentials
|
|
998
999
|
|
|
999
1000
|
if _SESSION is None:
|
|
1000
|
-
_SESSION =
|
|
1001
|
+
_SESSION = open_renewable_session(resolve_cloud_credentials(), transport=transport)
|
|
1001
1002
|
return StudioV4ActivityClient(_SESSION, transport=transport)
|
|
1002
1003
|
|
|
1003
1004
|
|
{mostlyright_data-0.31.9 → mostlyright_data-0.31.11}/src/mostlyright/data_harness/thin/recipe.py
RENAMED
|
@@ -168,7 +168,7 @@ _BOUNDED_STREAMING_POLICY = "bounded-streaming.v1"
|
|
|
168
168
|
FILE_DECODER_COORDINATES: frozenset[tuple[str, str]] = frozenset(
|
|
169
169
|
(family, version)
|
|
170
170
|
for family, versions in {
|
|
171
|
-
"archive.gzip": ("1.0.0", "1.1.0", "2.0.0"),
|
|
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
173
|
"archive.zip": ("1.0.0", "1.1.0", "1.2.0", "1.3.0", "1.4.0", "1.5.0", "3.0.0"),
|
|
174
174
|
"delimited_text": ("1.0.0", "1.1.0", "2.0.0"),
|
|
@@ -1431,9 +1431,10 @@ def register(
|
|
|
1431
1431
|
) -> dict[str, Any]:
|
|
1432
1432
|
"""``mr-data recipe FILE``: register one document and print what the server derived from it.
|
|
1433
1433
|
|
|
1434
|
-
Existing documents are linted before a credential is resolved. A keyless
|
|
1435
|
-
asks what Studio serves before local lint,
|
|
1436
|
-
before reporting capabilities. The brief and
|
|
1434
|
+
Existing documents are linted before a credential is resolved. A document using keyless
|
|
1435
|
+
grammar (``keyless_window`` or ``keyless_post``) asks what Studio serves before local lint,
|
|
1436
|
+
because an older schema may reject the grammar before reporting capabilities. The brief and
|
|
1437
|
+
delegation gates follow, and registration is last.
|
|
1437
1438
|
"""
|
|
1438
1439
|
|
|
1439
1440
|
document = read_document(Path(args.target))
|
|
@@ -1442,13 +1443,16 @@ def register(
|
|
|
1442
1443
|
v4.recipe_registration_body(document, "00000000-0000-0000-0000-000000000000")
|
|
1443
1444
|
# New grammar must be refused as unserved before local lint can diagnose its shape.
|
|
1444
1445
|
keyless = any(
|
|
1445
|
-
capability
|
|
1446
|
+
capability in recipe_explain.KEYLESS_CAPABILITIES
|
|
1447
|
+
for _, capability in recipe_explain.gated_grammar(document)
|
|
1446
1448
|
)
|
|
1447
1449
|
selected = client
|
|
1448
1450
|
served = []
|
|
1449
1451
|
if keyless:
|
|
1450
1452
|
selected = selected or _client(args)
|
|
1451
|
-
served = recipe_explain.registration_gate(
|
|
1453
|
+
served = recipe_explain.registration_gate(
|
|
1454
|
+
selected, document, lint=not getattr(args, "no_lint", False)
|
|
1455
|
+
)
|
|
1452
1456
|
check_document(document)
|
|
1453
1457
|
if not getattr(args, "no_lint", False):
|
|
1454
1458
|
refusal = lint_refusal(
|
|
@@ -35,6 +35,13 @@ from urllib.parse import parse_qsl, urlsplit
|
|
|
35
35
|
from uuid import UUID
|
|
36
36
|
|
|
37
37
|
from mostlyright.data_harness.thin import THIN_SCHEMA_PREFIX, numbered
|
|
38
|
+
from mostlyright.data_harness.thin.recipe_lint import (
|
|
39
|
+
KEYLESS_API_ADAPTER_ID,
|
|
40
|
+
ROW_REQUEST_ADAPTER_ID,
|
|
41
|
+
lint_document,
|
|
42
|
+
lint_refusal,
|
|
43
|
+
)
|
|
44
|
+
from mostlyright.data_harness.thin.studio_capabilities import KEYLESS_CAPABILITIES, KEYLESS_POST
|
|
38
45
|
from mostlyright.data_harness.thin.transport import ThinLaneError
|
|
39
46
|
from mostlyright.data_harness.thin.v4 import (
|
|
40
47
|
EXPLAIN_AND_FORECAST_REQUEST_VERSION,
|
|
@@ -131,6 +138,7 @@ CAPABILITY_NAMES: tuple[str, ...] = (
|
|
|
131
138
|
"snapshot_refresh",
|
|
132
139
|
"row_request_batch",
|
|
133
140
|
"keyless_window",
|
|
141
|
+
"keyless_post",
|
|
134
142
|
)
|
|
135
143
|
|
|
136
144
|
#: What a Studio that answers 404 or 405 from the explanation routes serves (README D27).
|
|
@@ -1228,12 +1236,17 @@ def _explanation(answer: Any, path: str) -> dict[str, Any]:
|
|
|
1228
1236
|
|
|
1229
1237
|
|
|
1230
1238
|
def explain_document_at_studio(
|
|
1231
|
-
client: StudioV4Client, document: Mapping[str, Any]
|
|
1239
|
+
client: StudioV4Client, document: Mapping[str, Any], *, lint: bool = True
|
|
1232
1240
|
) -> dict[str, Any] | None:
|
|
1233
1241
|
"""``POST /v4/recipes:explain-refresh``, or ``None`` when this Studio does not serve it.
|
|
1234
1242
|
|
|
1235
1243
|
Studio judges the document alone: it reads no predecessor, registers nothing and writes
|
|
1236
1244
|
nothing, which is what makes it safe to ask before a registration.
|
|
1245
|
+
|
|
1246
|
+
An older API's schema answers 422 to a keyless document, and also to a document that is
|
|
1247
|
+
simply wrong. So that 422 is checked against the local document rules and, unless ``lint`` is
|
|
1248
|
+
false, the local lint. A local fault is raised as itself. Only a document that passes both is
|
|
1249
|
+
reported as not served.
|
|
1237
1250
|
"""
|
|
1238
1251
|
|
|
1239
1252
|
body = {
|
|
@@ -1254,23 +1267,66 @@ def explain_document_at_studio(
|
|
|
1254
1267
|
if route_unavailable(refusal):
|
|
1255
1268
|
return None
|
|
1256
1269
|
keyless = [
|
|
1257
|
-
pointer
|
|
1270
|
+
(pointer, capability)
|
|
1258
1271
|
for pointer, capability in gated_grammar(document)
|
|
1259
|
-
if capability
|
|
1272
|
+
if capability in KEYLESS_CAPABILITIES
|
|
1260
1273
|
]
|
|
1261
1274
|
if keyless and getattr(refusal, "http_status", None) == 422:
|
|
1275
|
+
_raise_local_fault(document, lint=lint, cause=refusal)
|
|
1262
1276
|
# Older API schemas reject the document before they can answer capabilities.
|
|
1263
1277
|
# A schema rejection is never evidence that this grammar is served.
|
|
1264
1278
|
raise ThinLaneError(
|
|
1265
1279
|
GRAMMAR_NOT_SERVED_CODE,
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1280
|
+
not_served_detail(
|
|
1281
|
+
keyless,
|
|
1282
|
+
"Studio rejected the explanation document before reporting capabilities, "
|
|
1283
|
+
"and the document passes the local checks",
|
|
1284
|
+
known=False,
|
|
1285
|
+
),
|
|
1269
1286
|
) from refusal
|
|
1270
1287
|
raise
|
|
1271
1288
|
return _explanation(answer, EXPLAIN_REFRESH_PATH)
|
|
1272
1289
|
|
|
1273
1290
|
|
|
1291
|
+
def _raise_local_fault(document: Mapping[str, Any], *, lint: bool, cause: ThinLaneError) -> None:
|
|
1292
|
+
"""Raise the local finding a schema 422 may stand for, or return when there is none."""
|
|
1293
|
+
|
|
1294
|
+
from mostlyright.data_harness.thin import recipe as recipe_module
|
|
1295
|
+
|
|
1296
|
+
try:
|
|
1297
|
+
recipe_module.check_document(dict(document))
|
|
1298
|
+
except ThinLaneError as fault:
|
|
1299
|
+
raise fault from cause
|
|
1300
|
+
if lint:
|
|
1301
|
+
fault = lint_refusal(lint_document(document), subject="the recipe document")
|
|
1302
|
+
if fault is not None:
|
|
1303
|
+
raise fault from cause
|
|
1304
|
+
|
|
1305
|
+
|
|
1306
|
+
def not_served_detail(missing: Sequence[tuple[str, str]], why: str, *, known: bool) -> str:
|
|
1307
|
+
"""The not-served sentence: each pointer with the capability it needs, ``keyless_post`` first.
|
|
1308
|
+
|
|
1309
|
+
A keyless POST is the newest gated grammar, so where one is listed its method is named first
|
|
1310
|
+
and its repair is a GET in its place. ``known`` says whether Studio stated that the other
|
|
1311
|
+
members are unserved. After a schema 422 it did not, so they are not told to be removed.
|
|
1312
|
+
"""
|
|
1313
|
+
|
|
1314
|
+
ordered = sorted(missing, key=lambda item: item[1] != KEYLESS_POST)
|
|
1315
|
+
listed = ", ".join(f"{pointer} ({capability})" for pointer, capability in ordered)
|
|
1316
|
+
post = any(capability == KEYLESS_POST for _, capability in missing)
|
|
1317
|
+
others = any(capability != KEYLESS_POST for _, capability in missing)
|
|
1318
|
+
if post and others and known:
|
|
1319
|
+
repair = "send each keyless_post request as a GET, and remove the other members"
|
|
1320
|
+
elif post:
|
|
1321
|
+
repair = "send each keyless_post request as a GET"
|
|
1322
|
+
else:
|
|
1323
|
+
repair = "remove the member"
|
|
1324
|
+
return (
|
|
1325
|
+
f"not available on this Studio yet: {listed}. {why}. Nothing was registered. Wait for "
|
|
1326
|
+
f"the Studio deploy, or {repair}"
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
|
|
1274
1330
|
def explain_table_at_studio(client: StudioV4Client, table_id: str) -> dict[str, Any] | None:
|
|
1275
1331
|
"""``GET /v4/tables/{table_id}/refresh-explanation``, or ``None`` when it is not served."""
|
|
1276
1332
|
|
|
@@ -1308,7 +1364,9 @@ def gated_grammar(document: Mapping[str, Any]) -> list[tuple[str, str]]:
|
|
|
1308
1364
|
``row_filter`` on any collection discovery, and a per-row request collection whose
|
|
1309
1365
|
``pages.revisit`` states ``interval`` (4.14.0). A revisit on any other collection is old
|
|
1310
1366
|
grammar. ``batch`` on a per-row request (4.15.0). The public generic API, no-auth kind,
|
|
1311
|
-
millisecond endpoints and slice/pacing members (4.26.0) each need ``keyless_window``.
|
|
1367
|
+
millisecond endpoints and slice/pacing members (4.26.0) each need ``keyless_window``. A POST
|
|
1368
|
+
that presents no credential (4.49.0) needs ``keyless_post``: a ``public.https.api@1.0.0``
|
|
1369
|
+
request, or a per-row request under ``credential_mode: none``. Its pointer is the method.
|
|
1312
1370
|
"""
|
|
1313
1371
|
|
|
1314
1372
|
gated: list[tuple[str, str]] = []
|
|
@@ -1323,12 +1381,16 @@ def gated_grammar(document: Mapping[str, Any]) -> list[tuple[str, str]]:
|
|
|
1323
1381
|
prefix = f"/sources/{index}"
|
|
1324
1382
|
connector = source.get("connector")
|
|
1325
1383
|
if isinstance(connector, Mapping):
|
|
1326
|
-
|
|
1384
|
+
keyless_api = connector.get("adapter_id") == KEYLESS_API_ADAPTER_ID
|
|
1385
|
+
if keyless_api:
|
|
1327
1386
|
gated.append((f"{prefix}/connector/adapter_id", "keyless_window"))
|
|
1328
1387
|
generic = connector.get("generic_api")
|
|
1329
1388
|
auth = generic.get("auth") if isinstance(generic, Mapping) else None
|
|
1330
1389
|
if isinstance(auth, Mapping) and auth.get("kind") == "none":
|
|
1331
1390
|
gated.append((f"{prefix}/connector/generic_api/auth/kind", "keyless_window"))
|
|
1391
|
+
request = generic.get("request") if isinstance(generic, Mapping) else None
|
|
1392
|
+
if keyless_api and isinstance(request, Mapping) and request.get("method") == "POST":
|
|
1393
|
+
gated.append((f"{prefix}/connector/generic_api/request/method", "keyless_post"))
|
|
1332
1394
|
window = source.get("window")
|
|
1333
1395
|
merge = window.get("merge") if isinstance(window, Mapping) else None
|
|
1334
1396
|
partition = merge.get("partition") if isinstance(merge, Mapping) else None
|
|
@@ -1363,6 +1425,14 @@ def gated_grammar(document: Mapping[str, Any]) -> list[tuple[str, str]]:
|
|
|
1363
1425
|
gated.append(
|
|
1364
1426
|
(f"/sources/{index}/collection/discovery/request/batch", "row_request_batch")
|
|
1365
1427
|
)
|
|
1428
|
+
if (
|
|
1429
|
+
isinstance(connector, Mapping)
|
|
1430
|
+
and connector.get("adapter_id") == ROW_REQUEST_ADAPTER_ID
|
|
1431
|
+
and connector.get("credential_mode") == "none"
|
|
1432
|
+
and isinstance(request, Mapping)
|
|
1433
|
+
and request.get("method") == "POST"
|
|
1434
|
+
):
|
|
1435
|
+
gated.append((f"/sources/{index}/collection/discovery/request/method", "keyless_post"))
|
|
1366
1436
|
return gated
|
|
1367
1437
|
|
|
1368
1438
|
|
|
@@ -1381,7 +1451,9 @@ def snapshot_sources(document: Mapping[str, Any]) -> list[str]:
|
|
|
1381
1451
|
return named
|
|
1382
1452
|
|
|
1383
1453
|
|
|
1384
|
-
def registration_gate(
|
|
1454
|
+
def registration_gate(
|
|
1455
|
+
client: StudioV4Client, document: Mapping[str, Any], *, lint: bool = True
|
|
1456
|
+
) -> list[str]:
|
|
1385
1457
|
"""Refuse grammar this Studio does not serve, and return the snapshot warning when it applies.
|
|
1386
1458
|
|
|
1387
1459
|
Asks the document route only when the answer can change what happens: the document uses
|
|
@@ -1389,14 +1461,15 @@ def registration_gate(client: StudioV4Client, document: Mapping[str, Any]) -> li
|
|
|
1389
1461
|
other document registers exactly as before, with no extra request.
|
|
1390
1462
|
|
|
1391
1463
|
⚠ REFUSED BEFORE ANYTHING IS WRITTEN. The caller runs this before the brief gate's delegation
|
|
1392
|
-
append and before the registration, so a refusal here leaves nothing behind.
|
|
1464
|
+
append and before the registration, so a refusal here leaves nothing behind. ``lint`` is
|
|
1465
|
+
false under ``--no-lint``, so an older schema's 422 is then not checked against local lint.
|
|
1393
1466
|
"""
|
|
1394
1467
|
|
|
1395
1468
|
gated = gated_grammar(document)
|
|
1396
1469
|
snapshots = snapshot_sources(document)
|
|
1397
1470
|
if not gated and not snapshots:
|
|
1398
1471
|
return []
|
|
1399
|
-
answer = explain_document_at_studio(client, document)
|
|
1472
|
+
answer = explain_document_at_studio(client, document, lint=lint)
|
|
1400
1473
|
return grammar_gate(document, answer)
|
|
1401
1474
|
|
|
1402
1475
|
|
|
@@ -1406,18 +1479,16 @@ def grammar_gate(document: Mapping[str, Any], answer: Mapping[str, Any] | None)
|
|
|
1406
1479
|
gated = gated_grammar(document)
|
|
1407
1480
|
snapshots = snapshot_sources(document)
|
|
1408
1481
|
capabilities = served_capabilities(answer)
|
|
1409
|
-
missing = [
|
|
1482
|
+
missing = [
|
|
1483
|
+
(pointer, capability) for pointer, capability in gated if not capabilities[capability]
|
|
1484
|
+
]
|
|
1410
1485
|
if missing:
|
|
1411
1486
|
why = (
|
|
1412
|
-
"
|
|
1487
|
+
"This Studio does not serve the refresh explanation, so it predates 4.14.0"
|
|
1413
1488
|
if answer is None
|
|
1414
|
-
else "
|
|
1415
|
-
)
|
|
1416
|
-
raise ThinLaneError(
|
|
1417
|
-
GRAMMAR_NOT_SERVED_CODE,
|
|
1418
|
-
f"not available on this Studio yet: {', '.join(missing)} ({why}). Nothing was "
|
|
1419
|
-
"registered. Wait for the Studio deploy, or remove the member",
|
|
1489
|
+
else "This Studio's capabilities say it does not serve them"
|
|
1420
1490
|
)
|
|
1491
|
+
raise ThinLaneError(GRAMMAR_NOT_SERVED_CODE, not_served_detail(missing, why, known=True))
|
|
1421
1492
|
if snapshots and not capabilities["snapshot_refresh"]:
|
|
1422
1493
|
return [
|
|
1423
1494
|
f"{SNAPSHOT_WILL_NOT_REFRESH_YET}: {', '.join(repr(name) for name in snapshots)} "
|
|
@@ -1513,7 +1584,9 @@ def explain(args: argparse.Namespace, *, client: StudioV4Client | None = None) -
|
|
|
1513
1584
|
else:
|
|
1514
1585
|
document = recipe_module.read_document(Path(target))
|
|
1515
1586
|
selected = client or recipe_module._client(args)
|
|
1516
|
-
keyless = any(
|
|
1587
|
+
keyless = any(
|
|
1588
|
+
capability in KEYLESS_CAPABILITIES for _, capability in gated_grammar(document)
|
|
1589
|
+
)
|
|
1517
1590
|
if keyless:
|
|
1518
1591
|
answer = explain_document_at_studio(selected, document)
|
|
1519
1592
|
grammar_gate(document, answer)
|
|
@@ -1625,6 +1698,7 @@ __all__ = [
|
|
|
1625
1698
|
"EXPLAIN_REFRESH_PATH",
|
|
1626
1699
|
"EXPLAIN_SCHEMA",
|
|
1627
1700
|
"GRAMMAR_NOT_SERVED_CODE",
|
|
1701
|
+
"KEYLESS_CAPABILITIES",
|
|
1628
1702
|
"MIRRORED_RULE_CODES",
|
|
1629
1703
|
"NO_CAPABILITIES",
|
|
1630
1704
|
"ROUTES_REQUEST_VERSION",
|