mostlyright-data 0.29.2__tar.gz → 0.29.4__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.29.2 → mostlyright_data-0.29.4}/PKG-INFO +1 -1
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/pyproject.toml +1 -1
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/3-probe-read-a-source-before-committing-to-it.md +19 -4
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/making-a-table-incremental.md +67 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/readers.md +46 -1
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/sources.md +24 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/canonical.py +23 -3
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/table_manifest.py +1 -1
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/recipe.py +15 -6
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/recipe_explain.py +58 -3
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/session.py +21 -2
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/studio_capabilities.py +11 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4.py +2 -2
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_public.py +19 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/credential_store.py +14 -3
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/.gitignore +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/README.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/scripts/hatch_build.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/SKILL.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/agents/openai.yaml +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/1-open-the-page-and-the-link-to-it-in-the-first-message.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/2-brief-two-to-four-questions-each-with-a-recommended-answer.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/4-decide-say-what-you-chose-what-you-refused-and-ask-one-question.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/5-draft-one-recipe-document-one-call.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/6-build-one-run-sized-to-acquire-every-measured-source-whole.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/7-interrogate-ask-the-run-what-it-actually-delivered.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/8-fix-revise-the-document-and-register-it-again.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/9-present-only-what-survived-inspection-with-caveats.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/agent-protocol.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/autonomous-delivery.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/before-the-first-tool-call.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/boundaries.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/cloud-authentication-preflight.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/commands.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/cross-repository-protocol-reference.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/installation-parity.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/live-run.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/narrating-the-run.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/not-hosted-yet.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/one-install.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/prediction-labels.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/preparing-table-search.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/promote.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/receipts.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/recording-a-stream-venue.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/recovering-an-import-failure.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/reference-pages.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/required-protocol.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/source-credentials.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/the-one-thing-to-say-about-the-skill-itself.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/transforms.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/user-communication-contract.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/writing-a-decision-record.md +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/scripts/write_research_notebook.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/__init__.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/agent_protocol.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/formats.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/hosted_crawler_protocol.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/key_seam.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/page_coverage.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/part_check_evidence.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/session_probes.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/skill_assets.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/__init__.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/acquire.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/acquire_cancel.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/activity.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/approvals.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/categories.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/commands.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/dataset-categories-v1.json +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/download.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/fast_features.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/narrative.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/parity.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/probe.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/progress_rate.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/progress_vocabulary.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/propose.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/recipe_brief.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/recipe_forecast.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/recipe_lint.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/research.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/router.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/run_diagnose.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/runs.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/stream.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/stream_venue.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/transport.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/user_agent.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_artifacts.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_catalog.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_connected.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_connections.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_dataset_covers.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_datasets.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_handoff.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_narrative.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_query.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_reader.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_runs.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_search.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_secrets.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_stream.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_tables.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/vocabulary.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/__init__.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/attendance.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/clarification.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/cloud_auth.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/commands/__init__.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/commands/auth.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/commands/clarify.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/commands/login.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/commands/whoami.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/credential_native.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/credentials.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/login.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/path_kind.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/plain_file.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/remediation.py +0 -0
- {mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/render.py +0 -0
|
@@ -108,10 +108,25 @@ not an answer yet, so read it back again later. A status of `truncated` is an an
|
|
|
108
108
|
a limit: keep its limits and its evidence with whatever you record, and never call it complete. The
|
|
109
109
|
command takes no key and no Reader options, so a source that needs either, or that is not a CSV, is
|
|
110
110
|
read by the run below instead. So is any source that answers `capability_unavailable`, which means
|
|
111
|
-
this deployment cannot inspect it: do not retry it, and do not guess in its place.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
this deployment cannot inspect it: do not retry it, and do not guess in its place.
|
|
112
|
+
|
|
113
|
+
**Write down what each look found.** An inspection is not a run, and nothing on the page shows what
|
|
114
|
+
it found unless you write it down. Once a look has shown you the start of a file, write one
|
|
115
|
+
`mr-data dataset note DATASET_ID` for that source. The heading names the source and the outcome;
|
|
116
|
+
writing the same heading again revises that cell instead of adding one, so two sources need two
|
|
117
|
+
headings. The body says what the sample showed (the format and the columns) and how much of the file
|
|
118
|
+
it covered (the rows and bytes read, and whether it was cut short). Then count the source in
|
|
119
|
+
`--fact sources_probed` the next time you set the activity line.
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
mr-data dataset note DATASET_ID \
|
|
123
|
+
--heading "Gauge file A is a CSV with four columns, cut short at 1,000 rows" \
|
|
124
|
+
--json <<'EOF'
|
|
125
|
+
The look read the first 1,000 rows (41,208 bytes) of the file and stopped at the row limit, so it
|
|
126
|
+
describes the start of the file and not the whole file. It is a CSV with four columns: station_id,
|
|
127
|
+
observed_at, stage_m and quality_flag.
|
|
128
|
+
EOF
|
|
129
|
+
```
|
|
115
130
|
|
|
116
131
|
**Both obey the ordinary gate.** Registering needs the brief answered or delegated, so the run
|
|
117
132
|
happens AFTER stage 2's answers arrive — or at once under a delegation — and an inspection, which
|
|
@@ -560,3 +560,70 @@ again. Ask the user before passing either flag. Never pass one to clear a refusa
|
|
|
560
560
|
| calls on the first full after the change | every hit whose answer is not held | each hit that passes the filter, once, because that full reads fresh |
|
|
561
561
|
| calls on each weekly rebuild | every hit whose answer is not held | only new hits that pass the filter; the held answers are carried forward |
|
|
562
562
|
| `pages.max_fetches_per_run` | 36,874 | the forecast's suggestion after the first filtered run |
|
|
563
|
+
|
|
564
|
+
#### Keyless API windows (Studio 4.26)
|
|
565
|
+
|
|
566
|
+
Check `studio_capabilities.keyless_window` is literal `true` before using this grammar.
|
|
567
|
+
An absent or false capability means "not available on this Studio yet". Registration and
|
|
568
|
+
file explanation refuse with `THIN_GRAMMAR_NOT_SERVED` before local lint or registration;
|
|
569
|
+
an older API's schema 422 on the explanation also refuses. No dataset delegation is written.
|
|
570
|
+
The capability covers `public.https.api@1.0.0`, `generic_api.auth.kind: none`, endpoint
|
|
571
|
+
`epoch_millis`, the `epoch_millis_value` partition key, and the two request controls below. Existing credential-free file and collection
|
|
572
|
+
adapters do not require this capability.
|
|
573
|
+
|
|
574
|
+
For a public JSON/CSV API use `credential_mode: none`, an HTTPS `origin`, and `generic_api`
|
|
575
|
+
with exact `auth: {"kind":"none"}`. Requests are GET-only, with no body or credential reference.
|
|
576
|
+
Authenticated adapters retain their existing credential requirements. Declare both window
|
|
577
|
+
endpoints with `location: parameter` and corresponding required request parameters. Origin,
|
|
578
|
+
host allowlists and cursor next-URL restrictions still apply.
|
|
579
|
+
|
|
580
|
+
For Binance BTCUSDT one-minute klines, start the backfill at `2017-08-17T00:00:00Z` and request
|
|
581
|
+
`/api/v3/klines` with `symbol=BTCUSDT`, `interval=1m`, `limit=1000`, and parameter templates for
|
|
582
|
+
`startTime` and `endTime`. Use this request window:
|
|
583
|
+
|
|
584
|
+
```json
|
|
585
|
+
{
|
|
586
|
+
"request": {
|
|
587
|
+
"start": {"location": "parameter", "encoding": "epoch_millis", "bound": "inclusive", "parameter": "startTime"},
|
|
588
|
+
"end": {"location": "parameter", "encoding": "epoch_millis", "bound": "inclusive", "parameter": "endTime"},
|
|
589
|
+
"max_slice_seconds": 43200,
|
|
590
|
+
"min_interval_milliseconds": 1000
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Keep `granularity: day`, `timezone: UTC` and `merge.materialization: partition_replace`.
|
|
596
|
+
For the acquired `open_time_ms` column declare
|
|
597
|
+
`merge.partition: {"column":"open_time_ms","key":"epoch_millis_value"}` and
|
|
598
|
+
`merge.row_identity: ["open_time_ms"]`. This encoding maps the integer millisecond cell to
|
|
599
|
+
its UTC calendar day before transforms; it floors negative epochs correctly too.
|
|
600
|
+
The 43,200-second maximum cuts each UTC day into two 12-hour requests, each with at most 720
|
|
601
|
+
one-minute bars under the publisher's 1,000-row cap. Slices are requests, never new partitions.
|
|
602
|
+
An inclusive millisecond end renders the next slice boundary minus 1 ms; adjacent slices do not
|
|
603
|
+
overlap. An exclusive start renders the start minus 1 ms. The older date/epoch-second encodings
|
|
604
|
+
retain their day-step conversions. Slice bounds require both endpoints to use milliseconds.
|
|
605
|
+
`max_slice_seconds` is 1–86,400; `window.request.min_interval_milliseconds` is 1–3,600,000 and is supported on
|
|
606
|
+
the new public generic adapter, defaulting to 1,000 when omitted. Pacing applies across the
|
|
607
|
+
entire walk, including pagination, rather than resetting at each day.
|
|
608
|
+
|
|
609
|
+
After the full backfill, choose an hourly cadence in the agreed brief. Refresh re-reads the
|
|
610
|
+
current UTC day, flooring a catch-up start to midnight when prior coverage reached the next
|
|
611
|
+
midnight; it replaces that day's partition. Do not set `complete_period_only` when the open day
|
|
612
|
+
is needed. Size `limits.max_requests` for every slice and every pagination page across the full
|
|
613
|
+
walk; the global budget must allow it to finish. The 4,096-partition ceiling still counts days.
|
|
614
|
+
Python and Keel plan the same windows; large declared relations use the admitted Keel route,
|
|
615
|
+
with each day still subject to its in-memory ceiling. A declaration is not evidence of a
|
|
616
|
+
completed large ingestion: verify actual coverage from the run.
|
|
617
|
+
|
|
618
|
+
The [Reader example](readers.md#needs-studio-426) maps Binance's root array of 12-column arrays.
|
|
619
|
+
An explicit `table.partition` may use `{"column":"open_time_ms","encoding":"epoch_millis_value"}`;
|
|
620
|
+
that table encoding also requires `keyless_window`. The native refresh subset preserves this
|
|
621
|
+
partition column with exact `CAST(open_time_ms AS BIGINT) AS open_time_ms`; its current subset
|
|
622
|
+
requires `checks: []`. Verify the built values and coverage with bounded queries and receipts.
|
|
623
|
+
Bind the real dataset ID before Harness registration, following the brief workflow; the fixture
|
|
624
|
+
omits an ID rather than inventing an existing dataset.
|
|
625
|
+
The raw millisecond timestamp uses `epoch_millis_value`, never `iso_date_prefix`;
|
|
626
|
+
a transform does not run before source partition merging. Existing JSON cursor pagination is reused unchanged. XML readers
|
|
627
|
+
do not add extraction of arXiv OAI-PMH XML continuation tokens to JSON-pointer pagination.
|
|
628
|
+
Wikipedia's existing `public.https` date-in-path source remains a literal full fetch; this
|
|
629
|
+
example does not prove an entire historical date-path backfill.
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/readers.md
RENAMED
|
@@ -119,7 +119,7 @@ decoder. Zero matches and multiple matches both refuse.
|
|
|
119
119
|
| `raster.geospatial@1.0.0` | `format`, exact native `crs`, and an `x`/`y`/`width`/`height` window. |
|
|
120
120
|
| `satellite.sentinel_safe@1.0.0` | SAFE ZIP with exact spectral/quality member names, native CRS, quality layer, and an `x`/`y`/`width`/`height` window. |
|
|
121
121
|
| `image.rendered@1.0.0` | `format` plus a bounded `x`/`y`/`width`/`height` rendered-image crop. |
|
|
122
|
-
| `scientific.fits@1.0.0` | `
|
|
122
|
+
| `scientific.fits@1.0.0` | `band_id: "195"` plus an exact native `x`/`y`/`width`/`height` image window. |
|
|
123
123
|
| `satellite.himawari_hsd@1.0.0` | Certified HSD v1.3 single-segment B13 plus an exact native `x`/`y`/`width`/`height` radiance window. |
|
|
124
124
|
| `satellite.himawari_hsd_complete@1.0.0` | Complete HSD v1.3 scan ZIP: `band_id`, exact ordered `segment_members`, and a native `x`/`y`/`width`/`height` radiance window. |
|
|
125
125
|
| `satellite.xrit_image@1.0.0` | Exact ZIP prologue, eight ordered HRIT/LRIT image segments and epilogue; sealed channel/grid/navigation plus a bounded raw-count window. |
|
|
@@ -140,3 +140,48 @@ settings. Spreadsheet receipts also identify stored calculated values.
|
|
|
140
140
|
Pinned HTTPS Reader sources retrieve, decode, and parse in one attested Clean room call. JSON,
|
|
141
141
|
NDJSON, ZIP, TAR, GZIP, XLSX, delimited text, and single-record GRIB2 are supported when the
|
|
142
142
|
response matches the family's exact media-type, settings, and budget contract.
|
|
143
|
+
|
|
144
|
+
### Needs Studio 4.26
|
|
145
|
+
|
|
146
|
+
Check first: run `mr-data recipe explain RECIPE.json --json` and read `studio_capabilities`.
|
|
147
|
+
Use `public.https.api@1.0.0` only when `keyless_window` is literal `true`; otherwise report
|
|
148
|
+
"not available on this Studio yet". Registration and file explanation refuse with
|
|
149
|
+
`THIN_GRAMMAR_NOT_SERVED`, including an older API's schema 422 before capabilities.
|
|
150
|
+
|
|
151
|
+
The new adapter uses the existing `generic_api.response` Reader contract. For Binance klines,
|
|
152
|
+
state `format: json`, `records_pointer: ""`, and `reader.family_id: json.tabular` with
|
|
153
|
+
`family_version: 1.0.0` (or the already admitted `1.1.0`). Pin `options_digest` to the SHA-256
|
|
154
|
+
of the canonical decode options, rather than inserting a placeholder digest. These decode
|
|
155
|
+
options project a root JSON array of arrays, with RFC 6901 numeric indexes into each row:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"document_format": "json",
|
|
160
|
+
"records_pointer": "",
|
|
161
|
+
"expand": [],
|
|
162
|
+
"columns": [
|
|
163
|
+
{"name": "open_time_ms", "pointer": "/0"},
|
|
164
|
+
{"name": "open", "pointer": "/1"},
|
|
165
|
+
{"name": "high", "pointer": "/2"},
|
|
166
|
+
{"name": "low", "pointer": "/3"},
|
|
167
|
+
{"name": "close", "pointer": "/4"},
|
|
168
|
+
{"name": "volume", "pointer": "/5"},
|
|
169
|
+
{"name": "close_time_ms", "pointer": "/6"},
|
|
170
|
+
{"name": "quote_volume", "pointer": "/7"},
|
|
171
|
+
{"name": "trades", "pointer": "/8"},
|
|
172
|
+
{"name": "taker_buy_base_volume", "pointer": "/9"},
|
|
173
|
+
{"name": "taker_buy_quote_volume", "pointer": "/10"},
|
|
174
|
+
{"name": "unused", "pointer": "/11"}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Every column is required by default. The Reader projects values; it does not turn an epoch
|
|
180
|
+
millisecond cell into an ISO date. Declare numeric/decimal types in the table transform and
|
|
181
|
+
declare `window.merge.partition: {"column":"open_time_ms","key":"epoch_millis_value"}`.
|
|
182
|
+
The key maps the acquired integer millisecond value to its UTC day before transforms;
|
|
183
|
+
`merge.row_identity` is `["open_time_ms"]`. It also needs `keyless_window`. See
|
|
184
|
+
[the cap-safe window](making-a-table-incremental.md#keyless-api-windows-studio-426) for the
|
|
185
|
+
backfill, hourly cadence, shared Python/Keel windows and request limits. Cursor pagination
|
|
186
|
+
continues to use its existing JSON next-token pointer; XML OAI-PMH token parsing is not supplied
|
|
187
|
+
by this mapping.
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/sources.md
RENAMED
|
@@ -271,3 +271,27 @@ its upstream cannot refresh incrementally. A `closed` or `reference` source may
|
|
|
271
271
|
table, and a refresh restores it from the predecessor (ADR 0061). Do not split a table
|
|
272
272
|
to separate them. No per-row collection may derive its rows from a `closed` or `reference` source.
|
|
273
273
|
[Which plans refresh](making-a-table-incremental.md#which-plans-refresh) has the rest.
|
|
274
|
+
|
|
275
|
+
### Needs Studio 4.26
|
|
276
|
+
|
|
277
|
+
Check first: run `mr-data recipe explain RECIPE.json --json` and read `studio_capabilities`.
|
|
278
|
+
Use the following only when `keyless_window` is literal `true`; otherwise report "not available
|
|
279
|
+
on this Studio yet". The client refuses with `THIN_GRAMMAR_NOT_SERVED` before registration,
|
|
280
|
+
including when an older API rejects the explanation document with schema 422.
|
|
281
|
+
|
|
282
|
+
`public.https.api@1.0.0` is the credential-free generic HTTPS API adapter. State
|
|
283
|
+
`credential_mode: none`, `origin`, and `generic_api` with exact `auth: {"kind":"none"}`.
|
|
284
|
+
Use GET only: no request body, credential reference, token, secret header or secret query value.
|
|
285
|
+
This adapter does not relax authentication on the keyed adapters. The owned HTTPS origin,
|
|
286
|
+
host allowlist, bounded request budgets and cursor next-URL restrictions remain in force.
|
|
287
|
+
The existing generic response mapping and JSON/CSV pagination apply; a cursor needs the
|
|
288
|
+
publisher's actual next-token pointer. JSON-pointer pagination does not extract XML OAI-PMH tokens.
|
|
289
|
+
|
|
290
|
+
For Binance klines use parameter-windowed `startTime`/`endTime`, millisecond endpoints and
|
|
291
|
+
12-hour slices at one request per second. Follow the backfill and hourly-refresh example in
|
|
292
|
+
[making a table incremental](making-a-table-incremental.md#keyless-api-windows-studio-426)
|
|
293
|
+
and the root-array projection in [Readers](readers.md#needs-studio-426). Keep the day partition
|
|
294
|
+
with `epoch_millis_value` on the raw millisecond time column, and size the request ceiling
|
|
295
|
+
for all slices and pages. Do not substitute a whole-day query:
|
|
296
|
+
1,440 one-minute bars exceed Binance's 1,000-row response cap. Existing credential-free
|
|
297
|
+
`public.https` file fetches and `public.https.collection@2.0.0` keep their own contracts.
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/canonical.py
RENAMED
|
@@ -14,6 +14,7 @@ from typing import Any, NoReturn
|
|
|
14
14
|
|
|
15
15
|
MAX_CANONICAL_DEPTH = 64
|
|
16
16
|
MAX_CANONICAL_MEMBERS = 100_000
|
|
17
|
+
_MAX_INTEGER_LITERAL_LENGTH = len(str(-(2**63)))
|
|
17
18
|
MAX_CANONICAL_STRING_LENGTH = 1_000_000
|
|
18
19
|
MAX_CANONICAL_INTEGER = (1 << 63) - 1
|
|
19
20
|
|
|
@@ -35,7 +36,10 @@ def parse_json(value: str | bytes | bytearray | memoryview) -> Any:
|
|
|
35
36
|
|
|
36
37
|
|
|
37
38
|
def parse_foreign_json(
|
|
38
|
-
value: str | bytes | bytearray | memoryview,
|
|
39
|
+
value: str | bytes | bytearray | memoryview,
|
|
40
|
+
*,
|
|
41
|
+
max_members: int = MAX_CANONICAL_MEMBERS,
|
|
42
|
+
wide_integers: bool = False,
|
|
39
43
|
) -> Any:
|
|
40
44
|
"""Parse a third-party document with the same hardening, but accept fractional numbers.
|
|
41
45
|
|
|
@@ -59,9 +63,14 @@ def parse_foreign_json(
|
|
|
59
63
|
|
|
60
64
|
``max_members`` raises the member ceiling for one document whose size a route already bounds,
|
|
61
65
|
such as a table manifest listing thousands of parts.
|
|
66
|
+
|
|
67
|
+
``wide_integers`` reads an integer literal outside the signed 64-bit range as a finite float
|
|
68
|
+
rather than refusing the document. It is for an answer that carries a table's own values from
|
|
69
|
+
a publisher that writes a whole-valued double without a fraction or an exponent, so ``1e20``
|
|
70
|
+
arrives as ``100000000000000000000``. An integer inside the range is still an ``int``.
|
|
62
71
|
"""
|
|
63
72
|
|
|
64
|
-
return _parse(value, fractions="accept", max_members=max_members)
|
|
73
|
+
return _parse(value, fractions="accept", max_members=max_members, wide_integers=wide_integers)
|
|
65
74
|
|
|
66
75
|
|
|
67
76
|
def _parse(
|
|
@@ -69,6 +78,7 @@ def _parse(
|
|
|
69
78
|
*,
|
|
70
79
|
fractions: str,
|
|
71
80
|
max_members: int = MAX_CANONICAL_MEMBERS,
|
|
81
|
+
wide_integers: bool = False,
|
|
72
82
|
) -> Any:
|
|
73
83
|
if isinstance(value, str):
|
|
74
84
|
text = value
|
|
@@ -98,7 +108,7 @@ def _parse(
|
|
|
98
108
|
parsed = json.loads(
|
|
99
109
|
text,
|
|
100
110
|
object_pairs_hook=_reject_duplicate_pairs,
|
|
101
|
-
parse_int=_parse_integer,
|
|
111
|
+
parse_int=_parse_wide_integer if wide_integers else _parse_integer,
|
|
102
112
|
parse_float=_parse_fraction if accept_fractions else _reject_fraction,
|
|
103
113
|
parse_constant=_reject_constant,
|
|
104
114
|
)
|
|
@@ -318,6 +328,16 @@ def _parse_integer(value: str) -> int:
|
|
|
318
328
|
return parsed
|
|
319
329
|
|
|
320
330
|
|
|
331
|
+
def _parse_wide_integer(value: str) -> int | float:
|
|
332
|
+
"""Read an integer literal, as a float when it is outside the signed 64-bit range."""
|
|
333
|
+
|
|
334
|
+
if len(value) <= _MAX_INTEGER_LITERAL_LENGTH:
|
|
335
|
+
parsed = int(value)
|
|
336
|
+
if -MAX_CANONICAL_INTEGER - 1 <= parsed <= MAX_CANONICAL_INTEGER:
|
|
337
|
+
return parsed
|
|
338
|
+
return _parse_fraction(value)
|
|
339
|
+
|
|
340
|
+
|
|
321
341
|
def _parse_fraction(value: str) -> float:
|
|
322
342
|
"""Read a fractional literal from a foreign document, refusing a non-finite one."""
|
|
323
343
|
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/table_manifest.py
RENAMED
|
@@ -101,7 +101,7 @@ _SUPPORTED_MANIFEST_SCHEMAS = frozenset((TABLE_MANIFEST_SCHEMA, TABLE_MANIFEST_S
|
|
|
101
101
|
#: ``iso_month_prefix`` tables read as TABLE_MANIFEST_INVALID on 0.28.0 (planning#807).
|
|
102
102
|
#: ``tests/test_thin_table_manifest_encodings.py`` holds this equal to the vendored recipe schema.
|
|
103
103
|
MANIFEST_PARTITION_ENCODINGS: frozenset[str] = frozenset(
|
|
104
|
-
{"value", "iso_date_prefix", "iso_date_value", "iso_month_prefix"}
|
|
104
|
+
{"value", "iso_date_prefix", "iso_date_value", "iso_month_prefix", "epoch_millis_value"}
|
|
105
105
|
)
|
|
106
106
|
|
|
107
107
|
#: The two media types the format adds to the closed artifact vocabulary.
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/recipe.py
RENAMED
|
@@ -1181,13 +1181,21 @@ def register(
|
|
|
1181
1181
|
) -> dict[str, Any]:
|
|
1182
1182
|
"""``mr-data recipe FILE``: register one document and print what the server derived from it.
|
|
1183
1183
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
before
|
|
1187
|
-
whether this registration should happen at all. The registration itself is last.
|
|
1184
|
+
Existing documents are linted before a credential is resolved. A keyless-window document
|
|
1185
|
+
asks what Studio serves before local lint, because an older schema may reject the grammar
|
|
1186
|
+
before reporting capabilities. The brief and delegation gates follow; registration is last.
|
|
1188
1187
|
"""
|
|
1189
1188
|
|
|
1190
1189
|
document = read_document(Path(args.target))
|
|
1190
|
+
# New grammar must be refused as unserved before local lint can diagnose its shape.
|
|
1191
|
+
keyless = any(
|
|
1192
|
+
capability == "keyless_window" for _, capability in recipe_explain.gated_grammar(document)
|
|
1193
|
+
)
|
|
1194
|
+
selected = client
|
|
1195
|
+
served = []
|
|
1196
|
+
if keyless:
|
|
1197
|
+
selected = selected or _client(args)
|
|
1198
|
+
served = recipe_explain.registration_gate(selected, document)
|
|
1191
1199
|
check_document(document)
|
|
1192
1200
|
if not getattr(args, "no_lint", False):
|
|
1193
1201
|
refusal = lint_refusal(
|
|
@@ -1239,13 +1247,14 @@ def register(
|
|
|
1239
1247
|
)
|
|
1240
1248
|
delegated = getattr(args, "delegated", None)
|
|
1241
1249
|
blocks = delegation_blocks(delegated) if delegated is not None else None
|
|
1242
|
-
selected =
|
|
1250
|
+
selected = selected or _client(args)
|
|
1243
1251
|
# ⚠ THE CAPABILITY GATE COMES BEFORE ANYTHING IS WRITTEN. A document using grammar this
|
|
1244
1252
|
# Studio does not serve (`row_filter`, a per-row `interval` revisit) is refused here, before
|
|
1245
1253
|
# the delegation append below and before the registration, so a refusal leaves nothing
|
|
1246
1254
|
# behind. It asks Studio only when the document uses such grammar or a snapshot window, so
|
|
1247
1255
|
# every other registration makes exactly the calls it made before.
|
|
1248
|
-
|
|
1256
|
+
if not keyless:
|
|
1257
|
+
served = recipe_explain.registration_gate(selected, document)
|
|
1249
1258
|
warnings += served
|
|
1250
1259
|
warnings += sampled
|
|
1251
1260
|
warning_codes = [recipe_explain.SNAPSHOT_WILL_NOT_REFRESH_YET] if served else []
|
|
@@ -125,6 +125,7 @@ CAPABILITY_NAMES: tuple[str, ...] = (
|
|
|
125
125
|
"fresh_read_control",
|
|
126
126
|
"snapshot_refresh",
|
|
127
127
|
"row_request_batch",
|
|
128
|
+
"keyless_window",
|
|
128
129
|
)
|
|
129
130
|
|
|
130
131
|
#: What a Studio that answers 404 or 405 from the explanation routes serves (README D27).
|
|
@@ -1246,6 +1247,17 @@ def explain_document_at_studio(
|
|
|
1246
1247
|
except ThinLaneError as refusal:
|
|
1247
1248
|
if route_unavailable(refusal):
|
|
1248
1249
|
return None
|
|
1250
|
+
keyless = [pointer for pointer, capability in gated_grammar(document)
|
|
1251
|
+
if capability == "keyless_window"]
|
|
1252
|
+
if keyless and getattr(refusal, "http_status", None) == 422:
|
|
1253
|
+
# Older API schemas reject the document before they can answer capabilities.
|
|
1254
|
+
# A schema rejection is never evidence that this grammar is served.
|
|
1255
|
+
raise ThinLaneError(
|
|
1256
|
+
GRAMMAR_NOT_SERVED_CODE,
|
|
1257
|
+
f"not available on this Studio yet: {', '.join(keyless)} "
|
|
1258
|
+
"(Studio rejected the explanation document before reporting capabilities). "
|
|
1259
|
+
"Nothing was registered. Wait for the Studio deploy, or remove the member",
|
|
1260
|
+
) from refusal
|
|
1249
1261
|
raise
|
|
1250
1262
|
return _explanation(answer, EXPLAIN_REFRESH_PATH)
|
|
1251
1263
|
|
|
@@ -1286,14 +1298,42 @@ def gated_grammar(document: Mapping[str, Any]) -> list[tuple[str, str]]:
|
|
|
1286
1298
|
|
|
1287
1299
|
``row_filter`` on any collection discovery, and a per-row request collection whose
|
|
1288
1300
|
``pages.revisit`` states ``interval`` (4.14.0). A revisit on any other collection is old
|
|
1289
|
-
grammar. ``batch`` on a per-row request (4.15.0).
|
|
1301
|
+
grammar. ``batch`` on a per-row request (4.15.0). The public generic API, no-auth kind,
|
|
1302
|
+
millisecond endpoints and slice/pacing members (4.26.0) each need ``keyless_window``.
|
|
1290
1303
|
"""
|
|
1291
1304
|
|
|
1292
1305
|
gated: list[tuple[str, str]] = []
|
|
1306
|
+
table = document.get("table")
|
|
1307
|
+
partition = table.get("partition") if isinstance(table, Mapping) else None
|
|
1308
|
+
if isinstance(partition, Mapping) and partition.get("encoding") == "epoch_millis_value":
|
|
1309
|
+
gated.append(("/table/partition/encoding", "keyless_window"))
|
|
1293
1310
|
sources = document.get("sources")
|
|
1294
1311
|
for index, source in enumerate(sources if isinstance(sources, list) else ()):
|
|
1295
1312
|
if not isinstance(source, Mapping):
|
|
1296
1313
|
continue
|
|
1314
|
+
prefix = f"/sources/{index}"
|
|
1315
|
+
connector = source.get("connector")
|
|
1316
|
+
if isinstance(connector, Mapping):
|
|
1317
|
+
if connector.get("adapter_id") == "public.https.api@1.0.0":
|
|
1318
|
+
gated.append((f"{prefix}/connector/adapter_id", "keyless_window"))
|
|
1319
|
+
generic = connector.get("generic_api")
|
|
1320
|
+
auth = generic.get("auth") if isinstance(generic, Mapping) else None
|
|
1321
|
+
if isinstance(auth, Mapping) and auth.get("kind") == "none":
|
|
1322
|
+
gated.append((f"{prefix}/connector/generic_api/auth/kind", "keyless_window"))
|
|
1323
|
+
window = source.get("window")
|
|
1324
|
+
merge = window.get("merge") if isinstance(window, Mapping) else None
|
|
1325
|
+
partition = merge.get("partition") if isinstance(merge, Mapping) else None
|
|
1326
|
+
if isinstance(partition, Mapping) and partition.get("key") == "epoch_millis_value":
|
|
1327
|
+
gated.append((f"{prefix}/window/merge/partition/key", "keyless_window"))
|
|
1328
|
+
window_request = window.get("request") if isinstance(window, Mapping) else None
|
|
1329
|
+
if isinstance(window_request, Mapping):
|
|
1330
|
+
for endpoint in ("start", "end"):
|
|
1331
|
+
value = window_request.get(endpoint)
|
|
1332
|
+
if isinstance(value, Mapping) and value.get("encoding") == "epoch_millis":
|
|
1333
|
+
gated.append((f"{prefix}/window/request/{endpoint}/encoding", "keyless_window"))
|
|
1334
|
+
for member in ("max_slice_seconds", "min_interval_milliseconds"):
|
|
1335
|
+
if member in window_request:
|
|
1336
|
+
gated.append((f"{prefix}/window/request/{member}", "keyless_window"))
|
|
1297
1337
|
collection = source.get("collection")
|
|
1298
1338
|
if not isinstance(collection, Mapping):
|
|
1299
1339
|
continue
|
|
@@ -1348,6 +1388,16 @@ def registration_gate(client: StudioV4Client, document: Mapping[str, Any]) -> li
|
|
|
1348
1388
|
if not gated and not snapshots:
|
|
1349
1389
|
return []
|
|
1350
1390
|
answer = explain_document_at_studio(client, document)
|
|
1391
|
+
return grammar_gate(document, answer)
|
|
1392
|
+
|
|
1393
|
+
|
|
1394
|
+
def grammar_gate(
|
|
1395
|
+
document: Mapping[str, Any], answer: Mapping[str, Any] | None
|
|
1396
|
+
) -> list[str]:
|
|
1397
|
+
"""Admit only grammar explicitly served by an explanation; perform no writes."""
|
|
1398
|
+
|
|
1399
|
+
gated = gated_grammar(document)
|
|
1400
|
+
snapshots = snapshot_sources(document)
|
|
1351
1401
|
capabilities = served_capabilities(answer)
|
|
1352
1402
|
missing = [pointer for pointer, capability in gated if not capabilities[capability]]
|
|
1353
1403
|
if missing:
|
|
@@ -1455,10 +1505,15 @@ def explain(args: argparse.Namespace, *, client: StudioV4Client | None = None) -
|
|
|
1455
1505
|
answer = explain_table_at_studio(selected, registered["table_id"])
|
|
1456
1506
|
else:
|
|
1457
1507
|
document = recipe_module.read_document(Path(target))
|
|
1508
|
+
selected = client or recipe_module._client(args)
|
|
1509
|
+
keyless = any(capability == "keyless_window" for _, capability in gated_grammar(document))
|
|
1510
|
+
if keyless:
|
|
1511
|
+
answer = explain_document_at_studio(selected, document)
|
|
1512
|
+
grammar_gate(document, answer)
|
|
1458
1513
|
recipe_module.check_document(document)
|
|
1459
1514
|
subject = {"kind": "document", "table_id": None, "recipe_id": None, "recipe_digest": None}
|
|
1460
|
-
|
|
1461
|
-
|
|
1515
|
+
if not keyless:
|
|
1516
|
+
answer = explain_document_at_studio(selected, document)
|
|
1462
1517
|
capabilities = served_capabilities(answer)
|
|
1463
1518
|
# The mirror is judged under the fence Studio states, so the two can only disagree about
|
|
1464
1519
|
# the document; with no Studio to ask the fence reads closed, as a pre-4.14.0 Studio runs.
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/session.py
RENAMED
|
@@ -26,7 +26,12 @@ from typing import Any
|
|
|
26
26
|
from urllib.parse import quote, urlencode
|
|
27
27
|
from uuid import UUID
|
|
28
28
|
|
|
29
|
-
from mostlyright.data_harness.canonical import
|
|
29
|
+
from mostlyright.data_harness.canonical import (
|
|
30
|
+
MAX_CANONICAL_MEMBERS,
|
|
31
|
+
CanonicalJSONError,
|
|
32
|
+
parse_foreign_json,
|
|
33
|
+
parse_json,
|
|
34
|
+
)
|
|
30
35
|
from mostlyright.data_harness.thin.transport import (
|
|
31
36
|
MAX_JSON_RESPONSE_BYTES,
|
|
32
37
|
ThinLaneError,
|
|
@@ -620,6 +625,8 @@ class CloudClient:
|
|
|
620
625
|
expected: tuple[int, ...] = (200,),
|
|
621
626
|
absent: str | None = None,
|
|
622
627
|
maximum: int = MAX_TOKEN_RESPONSE_BYTES,
|
|
628
|
+
table_values: bool = False,
|
|
629
|
+
max_members: int = MAX_CANONICAL_MEMBERS,
|
|
623
630
|
) -> dict[str, Any]:
|
|
624
631
|
"""Make one Cloud call and return the object it answered with, or raise the refusal.
|
|
625
632
|
|
|
@@ -633,6 +640,14 @@ class CloudClient:
|
|
|
633
640
|
|
|
634
641
|
``absent`` is what this route's own 404 means, in the caller's words; see
|
|
635
642
|
:func:`cloud_refusal` for why one sentence could not serve both routes.
|
|
643
|
+
|
|
644
|
+
``table_values`` is for a route whose answer carries a table's own values. It admits a
|
|
645
|
+
finite fractional number, because a DOUBLE column or an average is not an integer, and it
|
|
646
|
+
reads an integer literal outside the signed 64-bit range as a float, because Cloud writes
|
|
647
|
+
a whole-valued double without a fraction. ``max_members`` is that answer's member ceiling,
|
|
648
|
+
for a route that states how large a page it may be handed. Every other hardening is
|
|
649
|
+
unchanged, and the default refuses all of it as it does for every answer this lane reads
|
|
650
|
+
as a document of its own.
|
|
636
651
|
"""
|
|
637
652
|
|
|
638
653
|
headers = {
|
|
@@ -651,7 +666,11 @@ class CloudClient:
|
|
|
651
666
|
maximum,
|
|
652
667
|
)
|
|
653
668
|
try:
|
|
654
|
-
parsed =
|
|
669
|
+
parsed = (
|
|
670
|
+
parse_foreign_json(raw, max_members=max_members, wide_integers=True)
|
|
671
|
+
if table_values
|
|
672
|
+
else parse_json(raw)
|
|
673
|
+
)
|
|
655
674
|
except CanonicalJSONError:
|
|
656
675
|
parsed = None
|
|
657
676
|
if status not in expected:
|
|
@@ -36,12 +36,14 @@ FRESH_READ_CONTROL = "fresh_read_control"
|
|
|
36
36
|
SNAPSHOT_REFRESH = "snapshot_refresh"
|
|
37
37
|
#: Served by every 4.15.0 API, which admits ``batch`` on a per-row request. An older API omits it.
|
|
38
38
|
ROW_REQUEST_BATCH = "row_request_batch"
|
|
39
|
+
KEYLESS_WINDOW = "keyless_window"
|
|
39
40
|
CAPABILITY_NAMES: tuple[str, ...] = (
|
|
40
41
|
ROW_FILTER,
|
|
41
42
|
PER_ROW_REVISIT_INTERVAL,
|
|
42
43
|
FRESH_READ_CONTROL,
|
|
43
44
|
SNAPSHOT_REFRESH,
|
|
44
45
|
ROW_REQUEST_BATCH,
|
|
46
|
+
KEYLESS_WINDOW,
|
|
45
47
|
)
|
|
46
48
|
|
|
47
49
|
#: What a Studio that serves neither explanation route serves.
|
|
@@ -96,6 +98,14 @@ def document_capabilities(client: StudioV4Client, document: Mapping[str, Any]) -
|
|
|
96
98
|
read. Studio judges the document alone and writes nothing.
|
|
97
99
|
"""
|
|
98
100
|
|
|
101
|
+
from mostlyright.data_harness.thin.recipe_explain import (
|
|
102
|
+
explain_document_at_studio,
|
|
103
|
+
gated_grammar,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if any(capability == KEYLESS_WINDOW for _, capability in gated_grammar(document)):
|
|
107
|
+
# Share the older-schema 422 translation with registration and file explanation.
|
|
108
|
+
return served_capabilities(explain_document_at_studio(client, document))
|
|
99
109
|
try:
|
|
100
110
|
answer = client.explain_recipe_refresh(document)
|
|
101
111
|
except ThinLaneError as refusal:
|
|
@@ -109,6 +119,7 @@ __all__ = [
|
|
|
109
119
|
"CAPABILITY_NAMES",
|
|
110
120
|
"FRESH_READ_CONTROL",
|
|
111
121
|
"GRAMMAR_NOT_SERVED_CODE",
|
|
122
|
+
"KEYLESS_WINDOW",
|
|
112
123
|
"NO_CAPABILITIES",
|
|
113
124
|
"PER_ROW_REVISIT_INTERVAL",
|
|
114
125
|
"ROW_FILTER",
|
|
@@ -147,8 +147,8 @@ from mostlyright.data_harness.thin.transport import ThinLaneError
|
|
|
147
147
|
#: ``JOB_INVALID`` outright, which in production was every refresh a collection epoch was offered
|
|
148
148
|
#: for rather than only the collection runs. This package must not reach a Studio older than the
|
|
149
149
|
#: commit it pins; ``docs/V4-WORKER-PROTOCOL.md`` states it beside the layout's own ordering rule.
|
|
150
|
-
PINNED_V4_OPENAPI_SOURCE_SHA256 = "
|
|
151
|
-
PINNED_V4_CONTRACT_VERSION = "4.
|
|
150
|
+
PINNED_V4_OPENAPI_SOURCE_SHA256 = "bf6312e73ba81725f9146dd6c8b10ff47ae57d1822ba695991aec03931d17d4d"
|
|
151
|
+
PINNED_V4_CONTRACT_VERSION = "4.26.0"
|
|
152
152
|
|
|
153
153
|
#: The ``schema_version`` every command body that predates 4.14.0 stamps: ``dataset activity``,
|
|
154
154
|
#: ``table rename``, ``table archive`` and ``dataset archive``. It is the number the 0.25 line
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_public.py
RENAMED
|
@@ -867,6 +867,20 @@ CLOUD_PUBLIC_TABLE_DOWNLOAD_URL_PATH = "/api/cli/public/tables/{table_id}/downlo
|
|
|
867
867
|
QUERY_SCHEMA = f"{THIN_SCHEMA_PREFIX}-v4-public-query.v1"
|
|
868
868
|
DOWNLOAD_URL_SCHEMA = f"{THIN_SCHEMA_PREFIX}-v4-public-download-url.v1"
|
|
869
869
|
|
|
870
|
+
#: Cloud bounds one credentialled query answer at 10 000 rows of at most 64 columns, and at 8 MiB
|
|
871
|
+
#: of JSON for the rows. The ceilings here admit the widest page those bounds allow: the row bytes
|
|
872
|
+
#: plus room for the members around them, and one member per value, per row and per member around
|
|
873
|
+
#: the rows.
|
|
874
|
+
MAX_PUBLIC_QUERY_ROWS = 10_000
|
|
875
|
+
MAX_PUBLIC_QUERY_COLUMNS = 64
|
|
876
|
+
MAX_PUBLIC_QUERY_ROW_BYTES = 8 * 1024 * 1024
|
|
877
|
+
MAX_PUBLIC_QUERY_ENVELOPE_BYTES = 1024 * 1024
|
|
878
|
+
MAX_PUBLIC_QUERY_ENVELOPE_MEMBERS = 16_384
|
|
879
|
+
MAX_PUBLIC_QUERY_RESPONSE_BYTES = MAX_PUBLIC_QUERY_ROW_BYTES + MAX_PUBLIC_QUERY_ENVELOPE_BYTES
|
|
880
|
+
MAX_PUBLIC_QUERY_RESPONSE_MEMBERS = (
|
|
881
|
+
MAX_PUBLIC_QUERY_ROWS * (MAX_PUBLIC_QUERY_COLUMNS + 1) + MAX_PUBLIC_QUERY_ENVELOPE_MEMBERS
|
|
882
|
+
)
|
|
883
|
+
|
|
870
884
|
|
|
871
885
|
class PublicQueryClient(CloudClient):
|
|
872
886
|
"""Authenticated public table query + download-url over the device-key client."""
|
|
@@ -878,6 +892,9 @@ class PublicQueryClient(CloudClient):
|
|
|
878
892
|
act="query a public table",
|
|
879
893
|
body=body,
|
|
880
894
|
absent="no public table has that id",
|
|
895
|
+
maximum=MAX_PUBLIC_QUERY_RESPONSE_BYTES,
|
|
896
|
+
table_values=True,
|
|
897
|
+
max_members=MAX_PUBLIC_QUERY_RESPONSE_MEMBERS,
|
|
881
898
|
)
|
|
882
899
|
|
|
883
900
|
def download_url(self, table_id: str) -> dict[str, Any]:
|
|
@@ -1091,6 +1108,8 @@ __all__ = [
|
|
|
1091
1108
|
"FETCH_SCHEMA",
|
|
1092
1109
|
"LOOKUP_SCHEMA",
|
|
1093
1110
|
"MAX_LIMIT",
|
|
1111
|
+
"MAX_PUBLIC_QUERY_RESPONSE_BYTES",
|
|
1112
|
+
"MAX_PUBLIC_QUERY_RESPONSE_MEMBERS",
|
|
1094
1113
|
"MAX_SAMPLE_LIMIT",
|
|
1095
1114
|
"QUERY_SCHEMA",
|
|
1096
1115
|
"SAMPLE_SCHEMA",
|
|
@@ -895,8 +895,13 @@ def _validate_private_file(
|
|
|
895
895
|
if os.name == "posix":
|
|
896
896
|
valid = valid and info.st_uid == os.geteuid() and stat.S_IMODE(info.st_mode) == 0o600
|
|
897
897
|
elif os.name == "nt":
|
|
898
|
-
|
|
899
|
-
|
|
898
|
+
# The DACL proof adds to the regular-file, single-link test as the owner and mode test does
|
|
899
|
+
# above. A second name is how a hard link steers a read or write to another path, and a
|
|
900
|
+
# DACL says nothing about it.
|
|
901
|
+
valid = (
|
|
902
|
+
valid
|
|
903
|
+
and path is not None
|
|
904
|
+
and _windows_acl_is_private(path, require_confidential=require_confidential)
|
|
900
905
|
)
|
|
901
906
|
if not valid:
|
|
902
907
|
raise _permission_refusal()
|
|
@@ -905,6 +910,11 @@ def _validate_private_file(
|
|
|
905
910
|
def _windows_acl_is_private(path: Path, *, require_confidential: bool = True) -> bool:
|
|
906
911
|
"""Prove only the current user, SYSTEM, or Administrators can mutate a Windows path.
|
|
907
912
|
|
|
913
|
+
The path must be owned by the current user, which is what lets an OWNER RIGHTS entry count as
|
|
914
|
+
the current user: it names whoever owns the path. CPython 3.12.4 and later give a folder made
|
|
915
|
+
with ``mkdir(mode=0o700)`` three entries, for SYSTEM, Administrators and OWNER RIGHTS, and none
|
|
916
|
+
for the user by name.
|
|
917
|
+
|
|
908
918
|
With ``require_confidential`` they must also be the only accounts that can read it. Only the
|
|
909
919
|
explicit secure-file credential needs that, when it is read or staged; replacing or removing it
|
|
910
920
|
exposes nothing. The folder, the lock file, the backend manifest and the DPAPI blob hold no
|
|
@@ -952,7 +962,8 @@ def _windows_acl_is_private(path: Path, *, require_confidential: bool = True) ->
|
|
|
952
962
|
return False
|
|
953
963
|
allowed = [current_sid]
|
|
954
964
|
allocated: list[ctypes.c_void_p] = []
|
|
955
|
-
|
|
965
|
+
# SYSTEM, Administrators, and OWNER RIGHTS, which stands for the owner proved above.
|
|
966
|
+
for text in ("S-1-5-18", "S-1-5-32-544", "S-1-3-4"):
|
|
956
967
|
sid = ctypes.c_void_p()
|
|
957
968
|
if not advapi32.ConvertStringSidToSidW(text, ctypes.byref(sid)) or not sid.value:
|
|
958
969
|
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/boundaries.md
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/commands.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/live-run.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/one-install.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/promote.md
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/receipts.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/skills/mr-data-build/references/transforms.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/__init__.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/agent_protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/key_seam.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/page_coverage.py
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/session_probes.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/skill_assets.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/__init__.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/acquire.py
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/activity.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/approvals.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/categories.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/download.py
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/narrative.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/parity.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/probe.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/propose.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/recipe_lint.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/research.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/router.py
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/runs.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/stream.py
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/transport.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/user_agent.py
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_catalog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_datasets.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_handoff.py
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_query.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_reader.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_runs.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_search.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_secrets.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_stream.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/v4_tables.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/thin/vocabulary.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/__init__.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/attendance.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/clarification.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/cloud_auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/commands/auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/credentials.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/login.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/path_kind.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/plain_file.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/remediation.py
RENAMED
|
File without changes
|
{mostlyright_data-0.29.2 → mostlyright_data-0.29.4}/src/mostlyright/data_harness/ux/render.py
RENAMED
|
File without changes
|