mostlyright-data 0.31.6__tar.gz → 0.31.8__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.6 → mostlyright_data-0.31.8}/PKG-INFO +1 -1
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/SKILL.md +31 -4
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +2 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/commands.md +3 -3
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/enriching-with-small-models.md +5 -1
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/preparing-table-search.md +7 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/promote.md +7 -0
- mostlyright_data-0.31.8/src/mostlyright/data_harness/thin/delivery.py +457 -0
- mostlyright_data-0.31.8/src/mostlyright/data_harness/thin/description_shape.py +120 -0
- mostlyright_data-0.31.8/src/mostlyright/data_harness/thin/table_view.py +384 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_datasets.py +6 -6
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_runs.py +135 -27
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_stream.py +23 -3
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_tables.py +71 -9
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/vocabulary.py +6 -3
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/render.py +28 -1
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/.gitignore +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/README.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/pyproject.toml +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/scripts/hatch_build.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/agents/openai.yaml +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/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.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/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.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/agent-protocol.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/boundaries.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/installation-parity.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/live-run.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/making-a-table-incremental.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/narrating-the-run.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/one-install.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/patterns.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/prediction-labels.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/readers.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/receipts.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/recipe-test.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/reference-pages.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/required-protocol.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/source-credentials.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/sources.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/transforms.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/user-communication-contract.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/__init__.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/agent_protocol.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/canonical.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/formats.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/key_seam.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/page_coverage.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/session_probes.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/skill_assets.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/table_manifest.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/__init__.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/acquire.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/activity.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/approvals.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/backoff.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/categories.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/commands.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/download.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/enrichment.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/matching_benchmark.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/narrative.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/parity.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/probe.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/propose.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_explain.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/recipe_test.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/refresh_rehearsal.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/release_check.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/research.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/router.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/run_prefix.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/runs.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/session.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/stream.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/studio_capabilities.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/support.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/table_search_contract.json +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/transport.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_matching.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_public.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_search.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_search_contract.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/__init__.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/attendance.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/clarification.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/credential_store.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/credentials.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/login.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
- {mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/src/mostlyright/data_harness/ux/remediation.py +0 -0
|
@@ -14,6 +14,15 @@ offer the small models (classify, extract, match) for the judgement no rule can
|
|
|
14
14
|
wait for the person to name a model. Steps 3, 4, 5, 8 and 10 say where, and
|
|
15
15
|
[enriching with small models](references/enriching-with-small-models.md) says how.
|
|
16
16
|
|
|
17
|
+
## Set the goal as a checklist
|
|
18
|
+
|
|
19
|
+
At the start of every build, write the goal as a checklist with the host's todo or plan tool:
|
|
20
|
+
TodoWrite in Claude Code, todowrite in opencode, or the plan tool where the host has one. List
|
|
21
|
+
each deliverable the brief names. Then list every delivery step: the description in the skill's
|
|
22
|
+
shape, the cover, search on the main table, the table view review, the refresh cadence and the
|
|
23
|
+
enrichment offer. Keep the list current as you work. Each item ends done, or skipped on purpose
|
|
24
|
+
with the reason written beside it. Do not report the build as done while an item is open.
|
|
25
|
+
|
|
17
26
|
## Four gates
|
|
18
27
|
|
|
19
28
|
Every build passes these four gates in order. Do not skip one. The numbered steps below carry
|
|
@@ -196,9 +205,11 @@ step 6. The stage 3 reference holds its rule. Gate 4 is step 6.
|
|
|
196
205
|
|
|
197
206
|
## Work in parallel
|
|
198
207
|
|
|
199
|
-
When the host can run subagents
|
|
200
|
-
|
|
201
|
-
|
|
208
|
+
When the host can run subagents, use them. The discovery lanes in step 4 and the probes of the
|
|
209
|
+
shortlisted sources must fan out: one subagent per lane and one per source, all started at the
|
|
210
|
+
same time. Run them one after another only when there is one lane or one source. Give other
|
|
211
|
+
independent work to subagents or background commands the same way. The lead agent keeps the
|
|
212
|
+
brief, every decision, every write and every word to the user.
|
|
202
213
|
|
|
203
214
|
- **What fans out.** The discovery lanes in step 4, one subagent per lane. Probing the sources
|
|
204
215
|
on the shortlist, one subagent per source, each with `mr-data source inspect`. Bounded gate-3
|
|
@@ -270,6 +281,21 @@ merely to obtain a green run. Read [recovery details](references/agent-protocol.
|
|
|
270
281
|
Then run `mr-data models suggest RUN_ID --json`. It reads the run's preview and names where
|
|
271
282
|
the table could use a deterministic derivation or a model call, and whether this deployment
|
|
272
283
|
serves that model. Confirm each lead against the rows before you offer it.
|
|
284
|
+
A succeeded full run's `mr-data status` and `mr-data watch` output carries a `delivery` block.
|
|
285
|
+
It names a missing cover, a description that is missing or out of the skill's shape,
|
|
286
|
+
natural-language search when the run's table has none, a view review when the table has no
|
|
287
|
+
saved view, a refresh cadence when none is recorded, and always the enrichment offer.
|
|
288
|
+
Complete every step before you report the build as done. Studio marks no main table, so you
|
|
289
|
+
judge it: skip the search and view steps for a supporting table and say so in one line. A
|
|
290
|
+
scratch table you test and demote owes none of these steps.
|
|
291
|
+
Review the view with `mr-data table view TABLE_ID --json`. It lists the view kinds this
|
|
292
|
+
deployment offers and the roles each one binds. Choose from the columns and a bounded sample,
|
|
293
|
+
check the choice with `--set VIEW.json --dry-run`, then save it. The plain table is fine when
|
|
294
|
+
it reads best, but save it as a deliberate choice.
|
|
295
|
+
Record the cadence under step 9's readiness rule. A fixed snapshot has no cadence to record:
|
|
296
|
+
write that decision as a dataset note and skip the step, saying so in one line.
|
|
297
|
+
Offering enrichment is mandatory. Accepting it is the person's choice, and a no is a complete
|
|
298
|
+
answer.
|
|
273
299
|
9. Any truncated source means a preview, not the requested complete dataset. Inspect coverage
|
|
274
300
|
even when the run succeeded. Untruncated acquisition does not prove publisher pagination, nested
|
|
275
301
|
records or population coverage is complete. A null coverage window does not mean all history was acquired.
|
|
@@ -285,7 +311,8 @@ merely to obtain a green run. Read [recovery details](references/agent-protocol.
|
|
|
285
311
|
repair, which is why the decision belongs in step 5. A table left not refresh-ready never
|
|
286
312
|
refreshes unless it declares `rebuild`; it does not become ready later.
|
|
287
313
|
Download and verify artifacts when the user requested bytes.
|
|
288
|
-
10.
|
|
314
|
+
10. Search is on by default for the dataset's main table. Supporting tables such as lookups and
|
|
315
|
+
code lists do not need it. Enable it, inspect search readiness and test meaningful queries against
|
|
289
316
|
the observed version as [search preparation](references/preparing-table-search.md) describes.
|
|
290
317
|
Search preparation runs independently: pending or failed indexing never means the data build
|
|
291
318
|
failed, and declared metadata alone never proves observed search quality. Before delivery,
|
|
@@ -300,6 +300,8 @@ uses must follow from the actual fields and coverage, without promising an outco
|
|
|
300
300
|
no bold field labels, em dashes,
|
|
301
301
|
semicolons, fake quotations or unsupported claims
|
|
302
302
|
- every `##` heading in DESCRIPTION.md is one of the five, in that relative order, none repeated
|
|
303
|
+
- Coverage and Columns are present. A succeeded full run's `delivery` block checks the opening,
|
|
304
|
+
the headings and the 4000-character bound, and names the description until it passes
|
|
303
305
|
|
|
304
306
|
**Every dataset gets a cover before its first run is presented.** Once the description is written,
|
|
305
307
|
generate one with `mr-data cover DATASET_ID "what the image should depict" --json`, describing the
|
{mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/commands.md
RENAMED
|
@@ -26,9 +26,9 @@ run in full.
|
|
|
26
26
|
| `mr-data recipe` | Register one recipe document (dataset, question, table plan, sources, transform, checks and units) in one call, and print the identifiers the server derived. The document is read first and every fault comes back in one refusal, each with the JSON pointer that names it. `--no-lint` sends it as written instead. A recipe that misses a feature that sets how fast a run fetches (a stated `pages.concurrency` under 64, a per-row request open to `batch`, a page walk open to `pagination.parallel`, or a Parquet file open to a ranged read) is refused `THIN_RECIPE_SLOW`, with each feature's pointer and fix, before anything is sent. Apply the fixes, or pass `--accept-slow "REASON"` with one sentence on why the slow path is right for this source. The receipt prints the reason and the features it covered. Registration refuses `THIN_BRIEF_MISSING` when the dataset's record carries no answered question and no delegation, and `THIN_RECIPE_DATASET_UNBOUND` when the document names no `dataset.id`. `--delegated "their words"` writes the delegation onto the dataset and registers in the same command, and clears the first of those two and never the second. `mr-data recipe show ID` reads one back. `mr-data recipe explain FILE` (or a recipe identifier) lists every reason its table can or cannot refresh, each with a rule code and Studio's suggested repair, and exits 2 when one blocks it. `mr-data recipe forecast RECIPE_ID` says what one run would ask of each source before it runs, and exits 3 when the run would overrun today's platform quota or one attempt. `--digest` is optional there, and without it the digest the recipe was registered with is forecast. `mr-data recipe readiness --table TABLE_ID --current-only` reads the revision one table runs now. The explanation's `fast_features` names each fast feature the recipe uses or misses, with a one-line fix. [Making a table incremental](making-a-table-incremental.md#the-loop-explain-forecast-run-diagnose) says when to use each. |
|
|
27
27
|
| `mr-data cover` | Generate and attach one branded 1200×630 dataset cover. Give it the dataset ID and what the image should depict; Studio fixes the model, single-color style, curated random palette, dimensions and storage. |
|
|
28
28
|
| `mr-data run` | Start one run against a registered recipe, named as `--recipe RECIPE_ID`, with `--digest RECIPE_DIGEST` when you want the run to assert which bytes it means. Neither is positional. The digest is the bare hex the registration receipt printed. Left out, the run reads it back from the registered recipe, which is the same value, because a recipe identifier names one revision. A full of a recipe that misses a fetch-speed feature is refused `THIN_RECIPE_SLOW`, as registration is, unless `--accept-slow "REASON"` is passed. Other modes print the missed features and are not refused. The mode is one of six: `sample`, `full`, `refresh`, `backfill`, `compact` and `replay`. Four have a shorthand flag (`--sample`, `--full`, `--refresh`, `--backfill`) and two do not, so write `--mode MODE`, which is accepted for every one of them. A refresh executes only Studio's persisted source-action plan, and a plan shape it does not refresh returns `RESYNC_REQUIRED` before acquisition. [Which plans refresh](making-a-table-incremental.md#which-plans-refresh) lists them, and `mr-data recipe explain` says which one a table is. Use `mr-data table resync TABLE_ID --request-id UUID` for an explicit full source reread, retaining that UUID if the response is uncertain. `--backfill` states the exact window with `--window START END`. `--mode replay --sources-from RUN_ID` asks Studio to run the registered revision against the RETAINED RAW INPUTS of one named successful run of the same table: nothing is fetched from this computer, nothing is acquired again, the result is compared against that run and never becomes the live version, and it neither asks for nor records an approval. Where Studio has replay switched off, or the named run is not successful, not the same table, or no longer retains its inputs, it answers a typed refusal. Report the code rather than retrying in another mode. `--sources-from` on any other mode is refused `THIN_ARGUMENT_INVALID` before anything is sent. `--max-rows` and `--max-source-bytes` bound ONE SOURCE rather than the finished table. `--max-rows` cuts rows after a source is fetched and decoded, so it does not shorten a collection walk. A sample with no clamp is refused: `CLAMPS_REQUIRED` from Studio, and `THIN_REQUEST_INVALID` from this client first. A table's first succeeded run goes live, truncated or not, so test on a scratch table as [the probe rule](3-probe-read-a-source-before-committing-to-it.md#the-probe-rule-test-on-a-scratch-table-never-on-the-real-table) says. A large run is held for a size confirmation (not a charge), which `--confirm` settles and whose printed `confirm_command` re-states every argument the request carried. `--approve-full RUN_ID` releases the full an existing legacy sample-first pair is holding once its preview has succeeded, naming either half of the pair. Where the deployment still wants a person at a browser it answers `THIN_INTERACTIVE_HUMAN_REQUIRED` and names the run page. `--retry RUN_ID` resumes a failed or cancelled progressive full from the pages it saved (`resume_capture_run_id` and the ceilings it carried; `retry_of_run_id` as well only when Studio admits a named retry). A failure that says the recipe must change is refused before anything is posted. Any other failed run is tried again when its triple carries `room_fault: true`, re-stating that run's own coordinate and ceilings and naming the failed run so Studio starts a new attempt, and is refused with the triple and the next step Studio's recovery category names when it is not. `room_fault` is Studio's `recovery.room_fault` where Studio states one. A source that kept refusing (`SOURCE_RATE_LIMITED`, `SOURCE_HTTP_5XX`, `SOURCE_PROXY_BUSY`) is sent anyway: Studio admits it 120 seconds after the failure and refuses `RUN_RETRY_TOO_SOON` with a Retry-After before then, so wait that long and run `--retry` again. `--cancel RUN` stops one. |
|
|
29
|
-
| `mr-data status` | Report which of the seven states one run is in, what it delivered and whether a ceiling cut it short, and — on a failure — what failed, where, and whether the fault was the execution room's rather than the recipe's. Exits non-zero on a failed run. `--diagnose` adds everything else Studio can say about the run: its progress, a per-row source's plan counts, its checkpoints, and how each attempt ended, with a lost attempt's cause marked as an inference. |
|
|
29
|
+
| `mr-data status` | Report which of the seven states one run is in, what it delivered and whether a ceiling cut it short, and — on a failure — what failed, where, and whether the fault was the execution room's rather than the recipe's. Exits non-zero on a failed run. `--diagnose` adds everything else Studio can say about the run: its progress, a per-row source's plan counts, its checkpoints, and how each attempt ended, with a lost attempt's cause marked as an inference. A succeeded full run carries `delivery`: the steps still owed before the build is done (a description missing or out of the skill's shape, a missing cover, natural-language search when the dataset's main table has none, a view review when the table has no saved view, a refresh cadence when none is recorded, and always the enrichment offer), each with its command. A read that fails adds a note, never a step. It never changes the exit code. |
|
|
30
30
|
| `mr-data runs` | Report this workspace's own runs: identifier, mode, state and creation time, plus the failure triple of any that failed and whether that failure was the execution room's. `--dataset DATASET_ID` narrows it to one dataset's runs, so "what have I run for this dataset?" is one scoped read, and a value that is not an identifier is refused `THIN_ARGUMENT_INVALID`. `--status` and `--mode` narrow it too and are refused `THIN_ARGUMENT_INVALID` for a word outside the seven states or the six modes; the three combine. Studio narrows before it cuts a page, so a filtered page holds the newest runs that match however many runs the workspace has. `--limit` says how many, and pages are followed to reach it; a listing that does not end says how many were read and suggests a smaller `--limit` or a narrower filter. The answer prints `next_cursor`, and `--cursor` continues from it. |
|
|
31
|
-
| `mr-data watch` | Stream one run's live progress under the durable event type of each stage, resuming across stream cuts. Exits non-zero when the run failed. `mr-data watch RUN_ID RUN_ID ...` follows up to 16 runs at once, prints one line as each ends, and returns once all have finished with one receipt per run (`failed_run_ids`, `refused_run_ids`); it exits 2 when any run failed and 1 when a run could not be watched. `--from-seq` and `--last-event-id` resume one run only. |
|
|
31
|
+
| `mr-data watch` | Stream one run's live progress under the durable event type of each stage, resuming across stream cuts. Exits non-zero when the run failed. `mr-data watch RUN_ID RUN_ID ...` follows up to 16 runs at once, prints one line as each ends, and returns once all have finished with one receipt per run (`failed_run_ids`, `refused_run_ids`); it exits 2 when any run failed and 1 when a run could not be watched. `--from-seq` and `--last-event-id` resume one run only. A full run that succeeds carries the same `delivery` block as `mr-data status`, and the multi-run receipt merges every run's steps. |
|
|
32
32
|
| `mr-data query` | Run one bounded read-only SQL statement over a finished run's own table and print the rows. Name the table `run_table` in the SQL, whatever the recipe called it. Bounded on rows, on answer bytes and on time, and refused by name when a bound is crossed. When no worker can answer, the refusal names `mr-data download RUN_ID` as `fallback_command`, which brings the table here to read locally. The question is asked through Mostly Right Cloud; while its usage paywall is on and the workspace is unsubscribed, each answered query counts toward the person's monthly allowance, and a spent allowance refuses `THIN_USAGE_LIMIT`. A refusal after the question was queued names its `query_id`; running the same command again reads that query back. |
|
|
33
33
|
| `mr-data search` | `search VERB` prepares and tests versioned search for a new table through Studio; no model or index runs on this computer. The verbs are `inspect`, `sample`, `values`, `propose`, `configure`, `validate`, `add-cases`, `explain`, `preview`, `evaluate`, `estimate`, `build`, `status`, `activate`, `cancel`, `rollback`, `locate` and `try`. `inspect`, `propose` and `validate` also take `--recipe FILE` before registration. `validate --spec` checks a spec and its evaluation cases against the pinned contract without sending anything. `locate` resolves evaluation-case rows to locators, and `try` runs one query against the live search. A build returns promptly with job coordinates, and an operation Studio does not serve answers a typed capability error. [Prepare search](preparing-table-search.md) says how it fits a build. |
|
|
34
34
|
| `mr-data peek` | Print the columns, their logical types and the first rows of one run's table. The type comes off the run's own column profile where the preview carries none, so `type` is null only for a run that sealed no profile. |
|
|
@@ -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. |
|
|
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. |
|
|
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. |
|
|
@@ -88,7 +88,11 @@ also keep the rows it settled from being sent at all: see [Needs Studio 4.14](#n
|
|
|
88
88
|
5. **After the run (step 8).** Run `mr-data models suggest RUN_ID --json`. It reads the run's
|
|
89
89
|
preview and names, column by column, a deterministic derivation or a model call that would
|
|
90
90
|
improve the table, each with `model_status` for the model it needs. It spends no call. Read
|
|
91
|
-
it as leads: confirm each against the rows before offering it.
|
|
91
|
+
it as leads: confirm each against the rows before offering it. The run's `delivery` block on
|
|
92
|
+
`mr-data status` and `mr-data watch` lists this offer as required, beside a missing
|
|
93
|
+
description, cover or main-table search. Search is its own required step, not part of this
|
|
94
|
+
offer. Make the offer before you report the build as done. Offering is
|
|
95
|
+
mandatory. Accepting is the person's choice. If nothing is worth adding, say so in one line.
|
|
92
96
|
6. **Deliver (step 10).** End the report with what would make the table better: the remaining
|
|
93
97
|
offers, deterministic ones first, each with the column it reads and what it would add. One
|
|
94
98
|
concrete sentence each, not a menu of everything possible.
|
|
@@ -57,6 +57,13 @@ missing search support only when `error` begins with
|
|
|
57
57
|
`THIN_SEARCH_CAPABILITY_UNAVAILABLE` as a capability limit too. Any other refusal is not
|
|
58
58
|
capability evidence. That includes 401, 403, 429, timeouts and invalid responses. Resolve it.
|
|
59
59
|
|
|
60
|
+
Search is on by default for the dataset's main table, whether or not the brief mentions it.
|
|
61
|
+
Supporting tables such as lookups, code lists and dimension tables do not need it. A succeeded
|
|
62
|
+
full run's `delivery` block names the search step whenever the run's table has no search, a
|
|
63
|
+
search whose build failed included. Studio marks no main table, so you judge which one it is.
|
|
64
|
+
Skip the step for a supporting table, while the capability is missing, or when the table has
|
|
65
|
+
nothing searchable, and say why in one line.
|
|
66
|
+
|
|
60
67
|
When the brief does not mention search, search is optional only while the capability is missing.
|
|
61
68
|
Then omit `table.search`, keep the drafted intent beside the recipe, and record the limit in the
|
|
62
69
|
dataset's Sources and decisions note. Never put that limit in the description, title or topics.
|
{mostlyright_data-0.31.6 → mostlyright_data-0.31.8}/skills/mr-data-build/references/promote.md
RENAMED
|
@@ -55,6 +55,13 @@ simply omitting the cadence command. Do not promise that ongoing work is disable
|
|
|
55
55
|
state proving it, and do not demote or archive a requested readable dataset as a workaround. If the
|
|
56
56
|
available controls cannot preserve the requested snapshot and refresh behavior, report that gap.
|
|
57
57
|
|
|
58
|
+
**A fixed snapshot is a recorded decision, not a missing cadence.** Studio has no cadence that
|
|
59
|
+
means never. Leave the table unscheduled, confirm `mr-data table TABLE_ID` shows no schedule,
|
|
60
|
+
and record the decision and its reason with
|
|
61
|
+
`mr-data dataset note DATASET_ID --heading "Fixed snapshot: no refresh" --markdown-file NOTE.md`.
|
|
62
|
+
Then skip the delivery block's cadence step, saying so in one line. The step stays listed, and
|
|
63
|
+
the note is the record.
|
|
64
|
+
|
|
58
65
|
This call records a schedule; it is not proof of data continuation. Schedule only a current recipe
|
|
59
66
|
that `mr-data recipe readiness --table TABLE_ID --current-only --json` reports as refresh-ready, or
|
|
60
67
|
one that declares `rebuild`. [Making a table incremental](making-a-table-incremental.md) says
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
"""The steps a succeeded full run still owes before the build is reported as done.
|
|
2
|
+
|
|
3
|
+
A full run that succeeded has data on the page. The page is not finished until it has a real
|
|
4
|
+
description and a cover, the main table has search and a deliberately chosen view, the table's
|
|
5
|
+
refresh cadence is recorded, and the person has been offered enrichment. This module names what
|
|
6
|
+
is missing, reading the dataset, the table's search, the table and its saved view once each, and
|
|
7
|
+
always names the enrichment offer. It never raises: a read that fails adds a note, never a step.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections.abc import Iterable, Mapping
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from mostlyright.data_harness.thin import description_shape
|
|
16
|
+
from mostlyright.data_harness.thin.session import StudioSession
|
|
17
|
+
from mostlyright.data_harness.thin.transport import ThinLaneError, Transport
|
|
18
|
+
from mostlyright.data_harness.thin.v4 import GET_TABLE_PATH, identifier
|
|
19
|
+
from mostlyright.data_harness.thin.v4_datasets import StudioV4DatasetClient
|
|
20
|
+
|
|
21
|
+
DELIVERY_VERSION = 1
|
|
22
|
+
|
|
23
|
+
#: Where the description's shape is written down.
|
|
24
|
+
SKILL_REFERENCE = "the build skill's references/5-draft-one-recipe-document-one-call.md"
|
|
25
|
+
|
|
26
|
+
#: What each conformance failure tells the person to fix.
|
|
27
|
+
_FAILURE_SAYS: dict[str, str] = {
|
|
28
|
+
"too_long": "it is over 4000 characters",
|
|
29
|
+
"missing_bold_opening": "it does not open with a bold first sentence",
|
|
30
|
+
"unknown_heading": "a ## heading is not one of the five",
|
|
31
|
+
"repeated_heading": "a ## heading is repeated",
|
|
32
|
+
"out_of_order_heading": "the ## headings are out of order",
|
|
33
|
+
"missing_coverage": "it has no ## Coverage section",
|
|
34
|
+
"missing_columns": "it has no ## Columns section",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#: The step names, in the order a person does them.
|
|
38
|
+
STEPS: tuple[str, ...] = (
|
|
39
|
+
"description",
|
|
40
|
+
"cover",
|
|
41
|
+
"search",
|
|
42
|
+
"table_view",
|
|
43
|
+
"cadence",
|
|
44
|
+
"enrichment_offer",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
_ENRICHMENT_WHY = (
|
|
48
|
+
"Offering enrichment is required on every full build. Confirm each lead against the rows, "
|
|
49
|
+
"then offer the worthwhile classification or extraction in one question. If nothing is worth "
|
|
50
|
+
"adding, say so in one line. A no is a complete answer."
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
_SEARCH_WHY = (
|
|
54
|
+
"Enable natural-language search on the dataset's main table: the one people query "
|
|
55
|
+
"directly. It is on by default, not an offer. Follow the skill's search flow in "
|
|
56
|
+
"references/preparing-table-search.md: search inspect, propose, validate, configure, build, "
|
|
57
|
+
"activate. Skip it, saying why in one line, when this is a supporting table (a lookup, a "
|
|
58
|
+
"code list, a dimension), when search inspect shows this deployment does not serve search "
|
|
59
|
+
"authoring, or when the table has nothing searchable."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
_VIEW_WHY = (
|
|
63
|
+
"Review how its rows read best and save that view. mr-data table view lists the view kinds "
|
|
64
|
+
"this deployment offers and the roles each one binds. Inspect the columns and a bounded "
|
|
65
|
+
"sample with mr-data peek, check the view with --set VIEW.json --dry-run, then save it. The "
|
|
66
|
+
"plain table is a valid choice when it reads best, but save it as a deliberate choice. Skip "
|
|
67
|
+
"it, saying why in one line, for a supporting table."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
_SNAPSHOT = (
|
|
71
|
+
"Studio has no cadence that means never. When the agreed answer is a fixed snapshot, leave "
|
|
72
|
+
"the table unscheduled, record that decision with mr-data dataset note {dataset_id} "
|
|
73
|
+
'--heading "Fixed snapshot: no refresh" --markdown-file NOTE.md, and skip this step, saying '
|
|
74
|
+
"so in one line. The step stays listed after the note, because the note is the record."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
_CADENCE_WHY = (
|
|
78
|
+
"Record it from the update answer in the brief. First confirm mr-data recipe readiness "
|
|
79
|
+
"--table {table_id} --current-only --json reports the current revision refresh-ready, or that "
|
|
80
|
+
"the recipe declares rebuild. A recipe that is not refresh-ready is a design fault to repair "
|
|
81
|
+
"with mr-data recipe explain, not a reason to skip this step. Then record the cadence and "
|
|
82
|
+
"the reasoning with promote. "
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
_NOT_READY_WHY = (
|
|
86
|
+
"Its current recipe is not refresh-ready: {blocker}. That is a design fault to repair, not a "
|
|
87
|
+
"state to wait out. Run mr-data recipe explain RECIPE_ID --json, repair the recipe, register "
|
|
88
|
+
"the revision and resync once, then record the cadence and the reasoning with promote. "
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
_PARKED_WHY = (
|
|
92
|
+
"A cadence was requested, but Studio holds it because the current recipe is not "
|
|
93
|
+
"refresh-ready: {blocker}. That is a design fault to repair, not a state to wait out. Run "
|
|
94
|
+
"mr-data recipe explain RECIPE_ID --json, repair the recipe, register the revision and resync "
|
|
95
|
+
"once. Studio restores the held schedule when the recipe is ready."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
#: Every block says this: a scratch table is a test, not the delivered dataset.
|
|
99
|
+
SCOPE = "A scratch table you test and demote owes none of these steps."
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def applies(run: Mapping[str, Any], *, succeeded: bool) -> bool:
|
|
103
|
+
"""Only a full run that succeeded owes delivery.
|
|
104
|
+
|
|
105
|
+
A sample is a test, and a refresh or a backfill adds rows to a dataset delivered before. The
|
|
106
|
+
caller folds a recipe test into ``succeeded``.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
return succeeded and run.get("mode") == "full"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _step(step: str, command: str, why: str) -> dict[str, str]:
|
|
113
|
+
return {"step": step, "command": command, "why": why}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _enrichment_step(run_id: str) -> dict[str, str]:
|
|
117
|
+
return _step("enrichment_offer", f"mr-data models suggest {run_id} --json", _ENRICHMENT_WHY)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _description_why(description: Any) -> str | None:
|
|
121
|
+
"""Why the description step is owed, or ``None`` when the description conforms."""
|
|
122
|
+
|
|
123
|
+
verdict = description_shape.check(description)
|
|
124
|
+
if verdict["conforms"]:
|
|
125
|
+
return None
|
|
126
|
+
sections = ", ".join(description_shape.HEADINGS)
|
|
127
|
+
if verdict["failures"] == ["empty"]:
|
|
128
|
+
return (
|
|
129
|
+
"The dataset has no description. Write one in the skill's shape: a bold first "
|
|
130
|
+
f"sentence, then ## sections from {sections}, in that order. See {SKILL_REFERENCE}."
|
|
131
|
+
)
|
|
132
|
+
said = ", ".join(_FAILURE_SAYS[code] for code in verdict["failures"])
|
|
133
|
+
return (
|
|
134
|
+
f"The description does not follow the skill's shape: {said}. Rewrite it with a bold first "
|
|
135
|
+
f"sentence, then ## sections from {sections}, in that order, Coverage and Columns always "
|
|
136
|
+
f"present. See {SKILL_REFERENCE}."
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def required_steps(
|
|
141
|
+
record: Mapping[str, Any], *, dataset_id: str, run_id: str
|
|
142
|
+
) -> list[dict[str, str]]:
|
|
143
|
+
"""The steps one dataset record still needs, the enrichment offer always last."""
|
|
144
|
+
|
|
145
|
+
steps: list[dict[str, str]] = []
|
|
146
|
+
why = _description_why(record.get("description"))
|
|
147
|
+
if why is not None:
|
|
148
|
+
steps.append(
|
|
149
|
+
_step(
|
|
150
|
+
"description",
|
|
151
|
+
f'mr-data dataset set {dataset_id} --name "TITLE" --topics "TOPIC,TOPIC" '
|
|
152
|
+
"--description-file DESCRIPTION.md",
|
|
153
|
+
why,
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
if not record.get("cover"):
|
|
157
|
+
steps.append(
|
|
158
|
+
_step(
|
|
159
|
+
"cover",
|
|
160
|
+
f'mr-data cover {dataset_id} "WHAT THE IMAGE SHOWS"',
|
|
161
|
+
"The dataset has no cover. Generate one.",
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
steps.append(_enrichment_step(run_id))
|
|
165
|
+
return steps
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _search_state(answer: Mapping[str, Any], table_id: str) -> str:
|
|
169
|
+
"""``active``, ``building`` (a search job is running for this version) or ``none``."""
|
|
170
|
+
|
|
171
|
+
if answer.get("schema_version") != "table-search-metadata.v1":
|
|
172
|
+
raise ValueError("not a table-search metadata answer")
|
|
173
|
+
if answer.get("table_id") != table_id:
|
|
174
|
+
raise ValueError("table-search metadata for another table")
|
|
175
|
+
if isinstance(answer.get("generation_id"), str) and answer["generation_id"]:
|
|
176
|
+
return "active"
|
|
177
|
+
# Only a running job counts: a revision whose build failed, or a spec never built, is owed.
|
|
178
|
+
if answer.get("preparation_state") == "pending":
|
|
179
|
+
return "building"
|
|
180
|
+
return "none"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _search_step(
|
|
184
|
+
client: StudioV4DatasetClient, table_id: str
|
|
185
|
+
) -> tuple[dict[str, str] | None, str | None]:
|
|
186
|
+
"""The search step when the run's table has no search, and any note. Never raises.
|
|
187
|
+
|
|
188
|
+
Studio marks no main table, so the step names the rule and the agent skips it, saying why,
|
|
189
|
+
for a supporting table.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
# Imported here: the search module's imports reach the run module, which imports this one.
|
|
194
|
+
from mostlyright.data_harness.thin.v4_search import TABLE_SEARCH_PATH
|
|
195
|
+
|
|
196
|
+
answer = client._call(
|
|
197
|
+
"GET",
|
|
198
|
+
TABLE_SEARCH_PATH.format(table_id=table_id),
|
|
199
|
+
query={"links": "false"},
|
|
200
|
+
expected=(200,),
|
|
201
|
+
)
|
|
202
|
+
state = _search_state(answer, table_id)
|
|
203
|
+
except ThinLaneError as refusal:
|
|
204
|
+
return None, _search_unchecked(table_id, f" ({refusal.code})")
|
|
205
|
+
except Exception: # a failed read must never fail the command
|
|
206
|
+
return None, _search_unchecked(table_id, "")
|
|
207
|
+
if state == "active":
|
|
208
|
+
return None, None
|
|
209
|
+
if state == "building":
|
|
210
|
+
return None, (
|
|
211
|
+
f"Search is building for this version. Read mr-data search status {table_id} "
|
|
212
|
+
"until it is active."
|
|
213
|
+
)
|
|
214
|
+
why = f"This run's table, {table_id}, has no search. {_SEARCH_WHY}"
|
|
215
|
+
return _step("search", "mr-data search inspect --recipe RECIPE.json --json", why), None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _search_unchecked(table_id: str, why: str) -> str:
|
|
219
|
+
return f"Search could not be checked{why}. Read mr-data search status {table_id}."
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _refusal_said(error: BaseException) -> str:
|
|
223
|
+
return f" ({error.code})" if isinstance(error, ThinLaneError) else ""
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _table_record(
|
|
227
|
+
client: StudioV4DatasetClient, table_id: str
|
|
228
|
+
) -> tuple[Mapping[str, Any] | None, str | None]:
|
|
229
|
+
"""The Studio table record, or a note saying it could not be read. Never raises."""
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
record = client._call("GET", GET_TABLE_PATH.format(table_id=table_id), expected=(200,))
|
|
233
|
+
except Exception as error: # a failed read must never fail the command
|
|
234
|
+
return None, (
|
|
235
|
+
f"View and cadence could not be checked{_refusal_said(error)}. "
|
|
236
|
+
f"Read mr-data table {table_id}."
|
|
237
|
+
)
|
|
238
|
+
if not isinstance(record, Mapping) or record.get("table_id") != table_id:
|
|
239
|
+
return None, f"View and cadence could not be checked. Read mr-data table {table_id}."
|
|
240
|
+
return record, None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def cadence_step(
|
|
244
|
+
record: Mapping[str, Any], *, table_id: str, dataset_id: str
|
|
245
|
+
) -> dict[str, str] | None:
|
|
246
|
+
"""The cadence step when a live table has no schedule and no cadence recorded.
|
|
247
|
+
|
|
248
|
+
Studio reports both as null until an explicit cadence is chosen, and while a readiness hold
|
|
249
|
+
parks a requested schedule. Studio also reads every such live table as ``held``, so ``held``
|
|
250
|
+
alone says nothing: a parked schedule or a named blocker is what makes it a repair.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
if record.get("promotion_status") != "live":
|
|
254
|
+
return None
|
|
255
|
+
if record.get("schedule") is not None or record.get("cadence") is not None:
|
|
256
|
+
return None
|
|
257
|
+
strategy = record.get("refresh_strategy")
|
|
258
|
+
strategy = strategy if isinstance(strategy, Mapping) else {}
|
|
259
|
+
blocker = strategy.get("blocker") or strategy.get("blocker_code")
|
|
260
|
+
said = blocker if isinstance(blocker, str) and blocker else "a blocking finding"
|
|
261
|
+
readiness = f"mr-data recipe readiness --table {table_id} --current-only --json"
|
|
262
|
+
if isinstance(strategy.get("held_schedule"), Mapping):
|
|
263
|
+
why = (
|
|
264
|
+
_PARKED_WHY.format(blocker=said)
|
|
265
|
+
if isinstance(blocker, str) and blocker
|
|
266
|
+
else "A cadence was requested, and Studio has not yet restored it. Read "
|
|
267
|
+
f"{readiness} and mr-data table {table_id} until the schedule is in force."
|
|
268
|
+
)
|
|
269
|
+
return _step(
|
|
270
|
+
"cadence",
|
|
271
|
+
readiness,
|
|
272
|
+
f"This run's table, {table_id}, has no refresh cadence in force. " + why,
|
|
273
|
+
)
|
|
274
|
+
snapshot = _SNAPSHOT.format(dataset_id=dataset_id)
|
|
275
|
+
if isinstance(blocker, str) and blocker:
|
|
276
|
+
return _step(
|
|
277
|
+
"cadence",
|
|
278
|
+
readiness,
|
|
279
|
+
f"This run's table, {table_id}, has no refresh cadence recorded. "
|
|
280
|
+
+ _NOT_READY_WHY.format(blocker=said)
|
|
281
|
+
+ snapshot,
|
|
282
|
+
)
|
|
283
|
+
return _step(
|
|
284
|
+
"cadence",
|
|
285
|
+
f'mr-data promote {table_id} --cadence "CRON OR every Nh" --why "WHY THIS FITS THE '
|
|
286
|
+
'SOURCE" --json',
|
|
287
|
+
f"This run's table, {table_id}, has no refresh cadence recorded. "
|
|
288
|
+
+ _CADENCE_WHY.format(table_id=table_id)
|
|
289
|
+
+ snapshot,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _cloud_view_client(transport: Transport) -> Any:
|
|
294
|
+
"""The Cloud table-view client on the stored device key. Tests replace it."""
|
|
295
|
+
|
|
296
|
+
# Imported here: the view module reads the credential store, which only this step needs.
|
|
297
|
+
from mostlyright.data_harness.thin import table_view
|
|
298
|
+
|
|
299
|
+
return table_view.cloud_client(transport)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _view_step(
|
|
303
|
+
record: Mapping[str, Any], *, table_id: str, transport: Transport
|
|
304
|
+
) -> tuple[dict[str, str] | None, str | None]:
|
|
305
|
+
"""The view step when the live table has no saved view, and any note. Never raises."""
|
|
306
|
+
|
|
307
|
+
from mostlyright.data_harness.thin import table_view
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
coordinates = table_view.live_coordinates(record)
|
|
311
|
+
except Exception:
|
|
312
|
+
coordinates = None
|
|
313
|
+
if coordinates is None:
|
|
314
|
+
return None, None
|
|
315
|
+
dataset_id, live = coordinates
|
|
316
|
+
try:
|
|
317
|
+
saved = table_view.saved_state(
|
|
318
|
+
_cloud_view_client(transport),
|
|
319
|
+
dataset_id=dataset_id,
|
|
320
|
+
table_id=table_id,
|
|
321
|
+
live_version_id=live,
|
|
322
|
+
)
|
|
323
|
+
except Exception as error: # a failed read must never fail the command
|
|
324
|
+
return None, (
|
|
325
|
+
f"The table view could not be checked{_refusal_said(error)}. "
|
|
326
|
+
f"Read mr-data table view {table_id}."
|
|
327
|
+
)
|
|
328
|
+
if saved:
|
|
329
|
+
return None, None
|
|
330
|
+
why = f"This run's table, {table_id}, has no saved view. {_VIEW_WHY}"
|
|
331
|
+
return _step("table_view", f"mr-data table view {table_id} --json", why), None
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _table_steps(
|
|
335
|
+
client: StudioV4DatasetClient,
|
|
336
|
+
run: Mapping[str, Any],
|
|
337
|
+
*,
|
|
338
|
+
dataset_id: str,
|
|
339
|
+
transport: Transport,
|
|
340
|
+
) -> tuple[list[dict[str, str]], list[str]]:
|
|
341
|
+
"""Search, view and cadence for the run's table, in that order, and any notes."""
|
|
342
|
+
|
|
343
|
+
try:
|
|
344
|
+
table_id = identifier(run.get("table_id"), "table identifier")
|
|
345
|
+
except Exception: # a record without a usable table identifier is still a delivered run
|
|
346
|
+
return [], ["Search, view and cadence could not be checked: the run names no table."]
|
|
347
|
+
steps: list[dict[str, str]] = []
|
|
348
|
+
notes: list[str] = []
|
|
349
|
+
search, note = _search_step(client, table_id)
|
|
350
|
+
if search is not None:
|
|
351
|
+
steps.append(search)
|
|
352
|
+
if note is not None:
|
|
353
|
+
notes.append(note)
|
|
354
|
+
record, note = _table_record(client, table_id)
|
|
355
|
+
if record is None:
|
|
356
|
+
notes.append(str(note))
|
|
357
|
+
return steps, notes
|
|
358
|
+
view, note = _view_step(record, table_id=table_id, transport=transport)
|
|
359
|
+
if view is not None:
|
|
360
|
+
steps.append(view)
|
|
361
|
+
if note is not None:
|
|
362
|
+
notes.append(note)
|
|
363
|
+
cadence = cadence_step(record, table_id=table_id, dataset_id=dataset_id)
|
|
364
|
+
if cadence is not None:
|
|
365
|
+
steps.append(cadence)
|
|
366
|
+
return steps, notes
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _unchecked(run_id: str, note: str) -> dict[str, Any]:
|
|
370
|
+
return {
|
|
371
|
+
"version": DELIVERY_VERSION,
|
|
372
|
+
"scope": SCOPE,
|
|
373
|
+
"required": [_enrichment_step(run_id)],
|
|
374
|
+
"note": note,
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def block(
|
|
379
|
+
client: StudioV4DatasetClient,
|
|
380
|
+
run: Mapping[str, Any],
|
|
381
|
+
run_id: str,
|
|
382
|
+
*,
|
|
383
|
+
transport: Transport | None = None,
|
|
384
|
+
) -> dict[str, Any]:
|
|
385
|
+
"""The ``delivery`` member for one succeeded full run. Never raises."""
|
|
386
|
+
|
|
387
|
+
try:
|
|
388
|
+
dataset_id = identifier(run.get("dataset_id"), "dataset identifier")
|
|
389
|
+
except Exception: # a record without a usable dataset identifier is still a delivered run
|
|
390
|
+
return _unchecked(run_id, "Presentation could not be checked: the run names no dataset.")
|
|
391
|
+
try:
|
|
392
|
+
record = client.dataset(dataset_id)
|
|
393
|
+
except ThinLaneError as refusal:
|
|
394
|
+
record = f" ({refusal.code})"
|
|
395
|
+
except Exception: # a failed read must never fail the command
|
|
396
|
+
record = ""
|
|
397
|
+
if not isinstance(record, Mapping):
|
|
398
|
+
why = record if isinstance(record, str) else ""
|
|
399
|
+
return _unchecked(
|
|
400
|
+
run_id,
|
|
401
|
+
f"Presentation could not be checked{why}. Read mr-data dataset show {dataset_id}.",
|
|
402
|
+
)
|
|
403
|
+
steps = required_steps(record, dataset_id=dataset_id, run_id=run_id)
|
|
404
|
+
table, notes = _table_steps(
|
|
405
|
+
client, run, dataset_id=dataset_id, transport=transport or client.transport
|
|
406
|
+
)
|
|
407
|
+
steps[len(steps) - 1 : len(steps) - 1] = table
|
|
408
|
+
delivered: dict[str, Any] = {"version": DELIVERY_VERSION, "scope": SCOPE, "required": steps}
|
|
409
|
+
if notes:
|
|
410
|
+
delivered["note"] = " ".join(notes)
|
|
411
|
+
return delivered
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def for_run(
|
|
415
|
+
session: StudioSession,
|
|
416
|
+
transport: Transport,
|
|
417
|
+
run: Mapping[str, Any],
|
|
418
|
+
run_id: str,
|
|
419
|
+
*,
|
|
420
|
+
succeeded: bool,
|
|
421
|
+
) -> dict[str, Any] | None:
|
|
422
|
+
"""The ``delivery`` member, or ``None`` when the run owes none."""
|
|
423
|
+
|
|
424
|
+
if not applies(run, succeeded=succeeded):
|
|
425
|
+
return None
|
|
426
|
+
try:
|
|
427
|
+
return block(
|
|
428
|
+
StudioV4DatasetClient(session, transport=transport), run, run_id, transport=transport
|
|
429
|
+
)
|
|
430
|
+
except Exception: # the block must never fail the command
|
|
431
|
+
return _unchecked(run_id, "Presentation could not be checked.")
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def merged(blocks: Iterable[Mapping[str, Any] | None]) -> dict[str, Any] | None:
|
|
435
|
+
"""One block for several runs: every step once, in run order."""
|
|
436
|
+
|
|
437
|
+
steps: list[dict[str, str]] = []
|
|
438
|
+
notes: list[str] = []
|
|
439
|
+
seen: set[tuple[Any, Any]] = set()
|
|
440
|
+
for one in blocks:
|
|
441
|
+
if not isinstance(one, Mapping):
|
|
442
|
+
continue
|
|
443
|
+
for step in one.get("required") or ():
|
|
444
|
+
key = (step.get("command"), step.get("why"))
|
|
445
|
+
if key in seen:
|
|
446
|
+
continue
|
|
447
|
+
seen.add(key)
|
|
448
|
+
steps.append(dict(step))
|
|
449
|
+
note = one.get("note")
|
|
450
|
+
if isinstance(note, str) and note not in notes:
|
|
451
|
+
notes.append(note)
|
|
452
|
+
if not steps:
|
|
453
|
+
return None
|
|
454
|
+
delivered: dict[str, Any] = {"version": DELIVERY_VERSION, "scope": SCOPE, "required": steps}
|
|
455
|
+
if notes:
|
|
456
|
+
delivered["note"] = " ".join(notes)
|
|
457
|
+
return delivered
|