mostlyright-data 0.31.8__tar.gz → 0.31.10__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.8 → mostlyright_data-0.31.10}/PKG-INFO +3 -2
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/README.md +2 -1
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/scripts/hatch_build.py +1 -1
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/commands.md +1 -1
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/making-a-table-incremental.md +12 -4
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/promote.md +11 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/readers.md +3 -2
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/source-credentials.md +2 -1
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/sources.md +72 -5
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/recipe.py +10 -6
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/recipe_explain.py +94 -20
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/recipe_lint.py +90 -14
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/router.py +2 -2
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/studio_capabilities.py +8 -1
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/table_search_contract.json +87 -18
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4.py +11 -21
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_artifacts.py +12 -3
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_runs.py +147 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_stream.py +26 -5
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_tables.py +276 -11
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/render.py +10 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/.gitignore +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/pyproject.toml +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/SKILL.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/agents/openai.yaml +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/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.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/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.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/agent-protocol.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/boundaries.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/enriching-with-small-models.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/installation-parity.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/live-run.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/narrating-the-run.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/one-install.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/patterns.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/prediction-labels.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/preparing-table-search.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/receipts.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/recipe-test.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/reference-pages.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/required-protocol.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/transforms.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/user-communication-contract.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/__init__.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/agent_protocol.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/canonical.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/formats.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/key_seam.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/page_coverage.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/session_probes.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/skill_assets.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/table_manifest.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/__init__.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/acquire.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/activity.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/approvals.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/backoff.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/categories.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/commands.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/delivery.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/description_shape.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/download.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/enrichment.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/matching_benchmark.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/narrative.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/parity.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/probe.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/propose.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/recipe_test.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/release_check.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/research.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/runs.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/session.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/stream.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/support.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/table_view.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/transport.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_matching.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_search.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/__init__.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/attendance.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/clarification.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/credentials.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/login.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
- {mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/src/mostlyright/data_harness/ux/remediation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mostlyright-data
|
|
3
|
-
Version: 0.31.
|
|
3
|
+
Version: 0.31.10
|
|
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
|
#
|
{mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/commands.md
RENAMED
|
@@ -47,7 +47,7 @@ run in full.
|
|
|
47
47
|
| `mr-data open` | Mint a single-use, short-lived address that continues this session in a browser at one dashboard path, and print it. `--open` hands it to the platform opener. The credential decides who mints: a device credential — what `mr-data login` stores — mints at Cloud, and a personal access token mints at Studio. |
|
|
48
48
|
| `mr-data promote` | Record the cadence one table refreshes on and the reasoning behind it. A table's first succeeded run goes live on its own, so this is not what makes it readable; it is idempotent on a table that is already live, and it is how a withdrawn table is put back. The Harness worker does not perform the catch-up or scheduled refresh; report those only when another deployed component returns durable evidence that it did. |
|
|
49
49
|
| `mr-data reschedule` | Change how often one live table refreshes, without the demote and promote that would reset its bookkeeping and buy a catch-up run. `--cadence` takes a cron expression or an interval such as `every 6h`; `--why` records the reasoning. It starts no run. `--lock` freezes the schedule so Studio stops adjusting it; `--unlock` lets it follow the source again. A table that cannot refresh answers `table_schedule_held` and exits 2, naming the two repairs: declare `rebuild`, or `table resync`. |
|
|
50
|
-
| `mr-data table` | Report what one table is serving and how its refresh schedule is going: the state in plain words, the schedule in force, the next refresh, the schedule that was asked for and the reason given for it, and — once Studio has settled on one — the interval it measured and how many source updates that rests on. Where Studio reports it, it also prints the table's refresh strategy: "rebuilds every 7d; next T" or "held: blocker". `table rename TABLE --name NAME` writes a new name, lowercase snake_case and unique within its dataset, fenced on that dataset's version and retried once if somebody else wrote first; nothing is rebuilt, because every run, version and schedule hangs off the identifier, which does not move. `table archive TABLE --confirm-name NAME` retires one table you name by the name it is called, and frees that name; a live table is refused until you `demote` it, and nothing built is deleted. `table archive TABLE [TABLE ...] --confirm DELETE` retires as many tables as you name at once: `DELETE` is the literal word, typed exactly, and it confirms the identifiers you typed rather than a name, so each table is retired under the name Studio is holding for it. Every table is attempted whatever the one before it answered, one line is printed for each, and the command exits non-zero if any was not retired — so read the outcome of each rather than the exit code alone. `--confirm-name` is refused when you name more than one table. `table view TABLE` says whether a live table has a saved view (`view_saved`) and lists the view kinds (`table`, `cards`, `places`, `products`, `events`, `jobs`, `people`, `articles`) and the roles each binds. There is no map or chart kind, and `places` are location cards. `table view TABLE --set VIEW.json` saves `{"version": 1, "template": KIND, "fields": {ROLE: COLUMN}}`, with optional `facts` (up to 4 columns) and `imageStyle`. `--dry-run` has Cloud validate it against the live table and save nothing. The file is checked here before anything is sent. Replacing a saved view needs its revision as `--expected-revision`, which MCP `get_table` reports, because Cloud serves no CLI read of it. A stale revision is refused `THIN_TABLE_VIEW_VIEW_CHANGED`. The command uses the device key. |
|
|
50
|
+
| `mr-data table` | Report what one table is serving and how its refresh schedule is going: the state in plain words, the schedule in force, the next refresh, the schedule that was asked for and the reason given for it, and — once Studio has settled on one — the interval it measured and how many source updates that rests on. Where Studio reports it, it also prints the table's refresh strategy: "rebuilds every 7d; next T" or "held: blocker". `table rename TABLE --name NAME` writes a new name, lowercase snake_case and unique within its dataset, fenced on that dataset's version and retried once if somebody else wrote first; nothing is rebuilt, because every run, version and schedule hangs off the identifier, which does not move. `table archive TABLE --confirm-name NAME` retires one table you name by the name it is called, and frees that name; a live table is refused until you `demote` it, and nothing built is deleted. `table archive TABLE [TABLE ...] --confirm DELETE` retires as many tables as you name at once: `DELETE` is the literal word, typed exactly, and it confirms the identifiers you typed rather than a name, so each table is retired under the name Studio is holding for it. Every table is attempted whatever the one before it answered, one line is printed for each, and the command exits non-zero if any was not retired — so read the outcome of each rather than the exit code alone. `--confirm-name` is refused when you name more than one table. `table view TABLE` says whether a live table has a saved view (`view_saved`) and lists the view kinds (`table`, `cards`, `places`, `products`, `events`, `jobs`, `people`, `articles`) and the roles each binds. There is no map or chart kind, and `places` are location cards. `table view TABLE --set VIEW.json` saves `{"version": 1, "template": KIND, "fields": {ROLE: COLUMN}}`, with optional `facts` (up to 4 columns) and `imageStyle`. `--dry-run` has Cloud validate it against the live table and save nothing. The file is checked here before anything is sent. Replacing a saved view needs its revision as `--expected-revision`, which MCP `get_table` reports, because Cloud serves no CLI read of it. A stale revision is refused `THIN_TABLE_VIEW_VIEW_CHANGED`. The command uses the device key. When the newest version is held from live, `table show` says `The newest version X is held: <reason>; serving Y`. `table accept-version TABLE --version VERSION_ID` releases that hold; live then derives from the newest unheld version. |
|
|
51
51
|
| `mr-data pin` | Ask Studio to freeze one table pointer on an exact version, for a rollback or a hold. |
|
|
52
52
|
| `mr-data unpin` | Ask Studio to resume pointer tracking; read the returned state before claiming it resumed. |
|
|
53
53
|
| `mr-data demote` | Ask Studio to withdraw the pointer and any schedule it owns, for one table or for as many as you name: `demote TABLE [TABLE ...]` withdraws them one after another, attempts every one of them whatever the one before it answered, prints a line for each, and exits non-zero if any is still live. Pair it with `table archive` when you are retiring a set: a live table cannot be archived, so it is withdraw-then-archive, two commands rather than a loop. |
|
|
@@ -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
|
{mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/skills/mr-data-build/references/promote.md
RENAMED
|
@@ -83,6 +83,17 @@ or a hold, `mr-data unpin TABLE_ID` resumes tracking, and `mr-data demote TABLE_
|
|
|
83
83
|
schedule and withdraws the live pointer. Read the resulting table state, but do not infer a
|
|
84
84
|
schedule, catch-up, or current bytes from the command name or from route acceptance.
|
|
85
85
|
|
|
86
|
+
**A succeeded run can be held from live.** Studio stamps every succeeded run with `verification`:
|
|
87
|
+
`verified`, or `degraded` with its reasons. When the new version would replace a better one, it
|
|
88
|
+
also stamps `live_hold`: the version is sealed and readable but not serving, and the table keeps
|
|
89
|
+
the version it had. `completeness` means the new version is degraded and the old one is verified.
|
|
90
|
+
`shrink` means it holds under half the old version's rows. `retention` means it sealed fewer rows
|
|
91
|
+
than the recipe's `verification.min_retained_ratio` allows. `mr-data status`, `watch` and
|
|
92
|
+
`receipt` say so, and `mr-data table show TABLE_ID` names the held version and the one serving.
|
|
93
|
+
Never report a held version as live. Find out why it is smaller or less complete first. If it is
|
|
94
|
+
right, `mr-data table accept-version TABLE_ID --version VERSION_ID` releases the hold. Live then
|
|
95
|
+
derives from the newest unheld version, so a newer version that has advanced since stays live.
|
|
96
|
+
|
|
86
97
|
To retire several tables, do it in two commands rather than a loop: `mr-data demote TABLE_ID
|
|
87
98
|
TABLE_ID ...` withdraws them, then `mr-data table archive TABLE_ID TABLE_ID ... --confirm DELETE`
|
|
88
99
|
retires them. `DELETE` is the literal word and nothing else is accepted; it confirms the
|
{mostlyright_data-0.31.8 → mostlyright_data-0.31.10}/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,
|
|
@@ -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.8 → mostlyright_data-0.31.10}/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.8 → mostlyright_data-0.31.10}/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",
|