Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a643ae5
test(34-01): add failing align + revision-collapse + parity-spike sca…
minereda Jul 14, 2026
6204a43
feat(34-01): SourceContract + align() pure-join core + ContractError/…
minereda Jul 14, 2026
038e6da
feat(34-01): compose CLI settlement label through align() (Path A)
minereda Jul 14, 2026
33514b9
docs(34-01): error-copy style guide (wave-1 contract, R-17)
minereda Jul 14, 2026
e323839
test(34-02): registry kind-axis + .label. invariant + contract-valida…
minereda Jul 14, 2026
f00100a
test(34-03): RED — collision prefix policy, window-optional obs()/laz…
minereda Jul 14, 2026
b42020b
feat(34-03): window-optional obs() + align collision/prefix policy + …
minereda Jul 14, 2026
adfd064
feat(34-02): registry kind-axis + .label. invariant + mr.contracts sp…
minereda Jul 14, 2026
d335126
merge(34): bring 34-02 registry/contracts into phase branch after 34-03
minereda Jul 14, 2026
91e959a
test(34-05): RED — generalized discover() token-scoring, alias-resolv…
minereda Jul 14, 2026
4b6400c
test(34-04): add failing tests for spine bridge, days()/forecasts()/l…
minereda Jul 14, 2026
68fd150
feat(34-04): mr.spine() + weather.days()/forecasts()/label.* + qc/tz_…
minereda Jul 14, 2026
9a1c523
feat(34-04): markets spine constructors + .label. settlement factorie…
minereda Jul 14, 2026
0a5a636
feat(34-05): generalize discover() into a cross-domain catalog search
minereda Jul 14, 2026
bd530a3
merge(34): bring 34-05 discover generalization into phase branch afte…
minereda Jul 14, 2026
386d524
test(34-06): RED — namespace inversion, legacy shims, error taxonomy,…
minereda Jul 14, 2026
924316b
test(34-07): shared align() conformance fixtures + Python/TS suites (…
minereda Jul 14, 2026
a0be505
feat(34-07): wire align conformance driver + registry-generated TS ca…
minereda Jul 14, 2026
44f9195
feat(34-06): invert root __all__, wire legacy shims, collapse errors,…
minereda Jul 14, 2026
6f3ade8
feat(34-06): per-domain pairs() quickstart + markets dataset()->pairs…
minereda Jul 14, 2026
1bd13c5
feat(34-06): claim the pairs token — specs/pairs.json + un-defer the …
minereda Jul 14, 2026
4c2f956
style(34-06): drop now-unused TYPE_CHECKING exception imports in alig…
minereda Jul 14, 2026
f03f4d1
merge(34): bring 34-06 namespace inversion into phase branch after 34-07
minereda Jul 14, 2026
cc9e93b
merge(34): bring main (econ v1.16.0 release) into phase34/registry-ap…
helloiamvu Jul 14, 2026
118b982
docs(34-08): 1.17 migration guide + route unknown-entity copy through…
helloiamvu Jul 14, 2026
1c1a9a9
chore(release): bump all dists to 1.17.0 for the registry-redesign re…
helloiamvu Jul 14, 2026
e4f336c
fix(34 review-iter-1): align temporal-key isolation + event-keyed laz…
helloiamvu Jul 14, 2026
41a1a60
fix(34 review-iter-2): normalize spine station key (v0.14.1 heritage)…
helloiamvu Jul 14, 2026
c79719d
fix(34 review-iter-3): markets spines share the normalized station key
helloiamvu Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/workflows/schema-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ on:
- "packages/core/src/mostlyright/_internal/merge/**"
- "packages/core/src/mostlyright/international.py"
- "packages/core/src/mostlyright/qc.py"
- "packages/core/src/mostlyright/registry/**"
- "packages/core/src/mostlyright/contracts/**"
- "packages/markets/src/mostlyright/markets/catalog/kalshi_stations.py"
- "packages/markets/src/mostlyright/markets/_per_event_station.py"
- "packages/markets/src/mostlyright/markets/polymarket_city_stations.json"
- "packages/weather/src/mostlyright/weather/**"
- "scripts/export_schemas.py"
- "schemas/**"
- "packages-ts/codegen/**"
- "packages-ts/*/src/**/generated/**"
- "packages-ts/weather/catalog.json"
- ".github/workflows/schema-drift.yml"
pull_request:
paths:
Expand All @@ -34,13 +38,17 @@ on:
- "packages/core/src/mostlyright/_internal/merge/**"
- "packages/core/src/mostlyright/international.py"
- "packages/core/src/mostlyright/qc.py"
- "packages/core/src/mostlyright/registry/**"
- "packages/core/src/mostlyright/contracts/**"
- "packages/markets/src/mostlyright/markets/catalog/kalshi_stations.py"
- "packages/markets/src/mostlyright/markets/_per_event_station.py"
- "packages/markets/src/mostlyright/markets/polymarket_city_stations.json"
- "packages/weather/src/mostlyright/weather/**"
- "scripts/export_schemas.py"
- "schemas/**"
- "packages-ts/codegen/**"
- "packages-ts/*/src/**/generated/**"
- "packages-ts/weather/catalog.json"
- ".github/workflows/schema-drift.yml"

jobs:
Expand Down Expand Up @@ -80,7 +88,7 @@ jobs:

- name: Check for schema or codegen drift
run: |
if ! git diff --exit-code schemas/ packages-ts/*/src/**/generated/; then
if ! git diff --exit-code schemas/ packages-ts/*/src/**/generated/ packages-ts/weather/catalog.json; then
echo "::error::Schema or codegen drift detected. Run 'uv run python scripts/export_schemas.py && pnpm codegen' locally and commit the result."
exit 1
fi
100 changes: 100 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,106 @@ All notable changes to `mostlyright`. The format follows [Keep a Changelog](http

## [Unreleased]

## [1.17.0] — 2026-07-14 — Registry API redesign: the `align` / `spine` / `sources` ladder + per-domain `pairs()`

Minor release, dual-SDK (PyPI 1.17.0 + npm 1.17.0 in lockstep). Phase 34 answers
the researcher feedback on the shipped composer ("too much magic — I can't tell a
feature from the target at a call site; let me build the DataFrame myself") by
splitting `dataset()`'s four fused concerns into **two orthogonal axes + one
composition operator**: SOURCES (`mr.<domain>.<table>(entity, window)` → X),
LABELS (`mr.<domain>.label.<name>(entity, window)` → a spine with `y`), and
**`mr.align(spine, *sources)`** — a leakage-guarded as-of join frozen at two
concepts. Every intermediate product is a plain, inspectable `pd.DataFrame`.

**This release is additive-plus-shims. Nothing you run today breaks** — the
default `research()` / `dataset()` output is byte-identical to 1.16 (live parity
gate 5/5 byte-green), every relocated symbol keeps an importable
`DeprecationWarning` shim, and **all 2.0 removals are DOCUMENTED only** (no shim
removed this release; ≥2 minors of warning runway).

Migration guide (where-did-each-kwarg-go table, the ladder, the symbol-move map,
TypeScript-on-1.17, econ-unchanged): [`docs/migration/v117-align-spine-sources.md`](docs/migration/v117-align-spine-sources.md).

### Added

- **`mr.align(spine, *sources)` — the frozen composition operator (D-34.01..05).**
A pure collapse + as-of/equality join + **per-source leakage audit** so a
raised `LeakageError` names WHICH source leaked and never blames a clean
co-aligned source. Signature is exactly `(spine, *sources)` — all policy
(revision authority, `pit_fidelity`, staleness) lives on the source specs. The
bitemporal `SourceContract` (event_time + knowledge_time, `revision_order`
tie-break, schema id + version, per-column units, license flag, `pit_fidelity`,
`valid_from` coverage, native frequency, `availability py|ts`) EXPRESSES the
legacy climate dedup (`report_type_priority` STRICT `>`, first-seen-wins) with
no special case inside `align()` (Path A — the cheapest falsifier, run first).
- **The LABEL / SPINE axis + raw spine constructors (D-34.09..12).** `mr.spine(df,
*, entity, decision_time, y)` (the BYO bridge; loud `ContractError` on a missing
mapped column, never guesses); `weather.days(stations, from_date, to_date, *,
tz_override=None)` (the public bare per-settlement-day spine; a list mints a
long-format panel byte-equal to the per-station loop); `weather.forecasts(entity,
..., source="iem_mos", model=None, models=None)` (a registered bitemporal source
wrapping the shipped forecast join — the landing site for the four forecast
kwargs); and the `.label.`-registered spine factories `weather.label.cli()` /
`weather.label.daily_extremes()` / `markets.*.label.settlement()` (y columns
byte-identical to the recipes). A **spine is the primitive; a label is a spine
factory** over the same domain constructor, so train and trade share the
calendar-minting site (the structural fix for backtest/live settlement skew).
- **Per-domain `pairs()` quickstart (D-34.21).** `weather.pairs(entity, from_date,
to_date, *, label="cli")` — capped at ≤5 params, two positional dates, a
docstring showing its own `weather.days()` → `weather.label.cli()` → `align()`
desugaring, and a visible **graduation error** naming spine+align (never a bare
`TypeError`) when you pass an unsupported kwarg. `markets.kalshi.pairs` /
`markets.polymarket.pairs` are the renamed delegators.
- **Generalized `discover(text?, **filters)` (D-34.13/14).** Cross-domain catalog
search over the registration metadata (id/kind/domain/columns/units/availability/
license/PIT fidelity) with deterministic field-weighted token scoring (no fuzzy
dep), per-domain entity **alias resolvers** (`"nyc"` → KNYC/KLGA, preserving the
cross-venue settlement note), a runnable language-tagged `{py, ts}` `usage` per
row, and an `availability` runtime filter. Keyword-only `city=` still works.
- **Cross-SDK `align()` conformance fixtures (D-34.14/25).** A shared four-axis
JSON suite (tie-break authority ⟂ arrival + first-seen tie, `<=` boundary
inclusivity, nanosecond precision, NaN preservation) under
`tests/fixtures/parity/conformance/`, driven as a HARD gate by Python against
the real join core and read by the identical fixtures from the TS suite. A
registry-generated `@mostlyrightmd/weather/catalog` lazy static-JSON subpath
(byte-deterministic under `export_schemas.py --check`), kept OUT of the
size-budgeted meta barrel (TS-BUNDLE-01).
- **Migration guide + error-copy conformance (D-34.24/26).**
`docs/migration/v117-align-spine-sources.md` (the 26-kwarg → new-home table, the
four forecast kwargs → `weather.forecasts()`, label's 5 jobs → 4 call shapes,
the three orphan kwargs homed, the namespace-inversion symbol map, TypeScript-on-1.17,
the econ-unchanged F-2 follow-up, and PIT-transform-out-of-scope). Every
user-facing error string from Phase 34 is audited against
`docs/error-copy-style-guide.md` (four required fields: source id, row/entity,
both timestamps where temporal, concrete fix) and built through the single
`_errmsg` construction path — including the unknown-entity `did you mean`
suggestion (`EntityNotFoundError`).

### Changed

- **Namespace inversion — root now reads as the product pitch (D-34.20).** Root =
machinery (`align`, `spine`, `discover`, `provenance`, `LeakageError` /
`ContractError` / `NoDataError`, `registry`, `contracts`, `__version__`) +
domains (`weather` / `markets` / `econ`). A symbol stays top-level only if it is
meaningful without naming a domain.
- **Collapsed error taxonomy (D-34.23).** `MostlyRightError` → `LeakageError` /
`ContractError` / `NoDataError`; `LiveStreamError` re-parented under `NoDataError`
(additive MRO) so `issubclass(NoCWOPDataError, NoDataError)` is True. The three
are importable one deep (`except mr.LeakageError:`).

### Deprecated

- **Relocated root symbols keep byte-stable importable shims (removal at 2.0).**
`from mostlyright import research/dataset/Station/CATALOG/StationCatalog` and
`mostlyright.live` now resolve via a once-per-session `DeprecationWarning` shim
naming the `mostlyright.weather.*` home (`mostlyright.research is
mostlyright.weather.research` — same object; parity is output bytes, not import
path). `markets.kalshi.dataset()` / `markets.polymarket.dataset()` → `pairs()`
with a deprecation shim. **Removal target: 2.0 (≥2 minors out), documented only.**
- **The D-22 label-default `FutureWarning` is GONE (D-34.22).** The 2.0 flip
(`"cli"` → `None`) is cancelled — there is no implicit label default in the new
surface, so an OMITTED label stays `"cli"` byte-stable with no warning, forever.

## [1.16.0] — 2026-07-14 — Economic indicators vertical: first published `mostlyrightmd-econ` / `@mostlyrightmd/econ` dist

First release of the **econ vertical** — CPI, PPI, nonfarm payrolls, unemployment,
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ table, coverage map, auto-routing, cheap-CONUS steering, DSRF gating, the 28 TB
- **Quickstart + concepts:** <https://mostlyright.md/docs/sdk/>
- **`dataset()` — the alignment engine:** [`docs/dataset.md`](docs/dataset.md) — the label axis, the contributor protocol, panels, and the honest with/without comparison.
- **Source identity — the cross-vertical kwarg contract:** [`docs/source-identity.md`](docs/source-identity.md) — `source=` / `delivery=` / grain, the labels-only firewall, and the fetch-provenance ledger.
- **Migrating to 1.17.0 — the `align` / `spine` / `sources` ladder:** [`docs/migration/v117-align-spine-sources.md`](docs/migration/v117-align-spine-sources.md) — the where-did-each-`dataset()`-kwarg-go table (all 26), `label`'s 5 jobs → 4 call shapes, per-domain `pairs()`, the namespace-inversion symbol map, TypeScript-on-1.17, and the econ-unchanged note. Additive-plus-shims; the D-22 `FutureWarning` is gone.
- **Migrating to 1.15.0:** [`docs/migration-1.15.md`](docs/migration-1.15.md) — the D-22 default-label `FutureWarning`, `include_*` → `features=`, and `contract=` → the markets layer.
- **API reference:** <https://mostlyright.md/docs/sdk/> (Python + TypeScript reference auto-generated per release)
- **Migration from the legacy hosted-API client:** <https://mostlyright.md/docs/sdk/migration/legacy/>
Expand Down
80 changes: 80 additions & 0 deletions docs/error-copy-style-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Error-copy style guide

**Status:** wave-1 contract (Phase 34 01, R-17). Every user-facing error string
written in waves 1-4 conforms to this guide. Plan **34-08** performs only the
final audit against it — it does NOT author error copy.

Errors are product surface: they are the moat's voice. A researcher who trips the
leakage guard should learn, from the message alone, *which* source leaked, *by
how much*, and *exactly what to do next* — without opening a traceback or the
source.

## The four required fields

Every user-facing error raised by `align()` / the source contract MUST carry:

1. **Offending source id** — the `SourceContract.id` (e.g. `weather.climate.cli`)
at fault. In a multi-source `align()`, name the ONE source that failed; never
blame a clean co-aligned source (R-22).
2. **Row / entity** — the entity (station, ticker, …) and/or the row index the
error is about, so the user can locate it in their own frame.
3. **Both timestamps, where the error is temporal** — for a leak, print BOTH the
leaking `knowledge_time` and the `decision_time` cutoff it crossed. One
timestamp alone is not actionable.
4. **Concrete fix / next call** — the exact next action: a filter to apply, a
contract field to rename, or a verb to call (e.g. `mr.provenance(frame)`).

Non-temporal errors (a malformed contract) satisfy field 3 vacuously.

## Single construction path

All `align()` / contract raises build their message text through the shared
helper at **`packages/core/src/mostlyright/_errmsg.py`**:

- `leakage_message(...)` → the copy for `LeakageError` (fields 1-4).
- `contract_message(...)` → the copy for `ContractError` (fields 1, 2, 4).

Do NOT hand-format error strings at the raise site. The exception CLASS
(`core/exceptions.py`) holds the STRUCTURED payload (`source`,
`violating_count`, `sample_violations`, `field`, …); the `_errmsg` builder holds
the WORDING. Keeping wording in one module is what lets 34-08 audit copy in one
place and what keeps Python/TS copy in lockstep.

## Worked example: `LeakageError`

A two-source `align()` where only `weather.forecasts.gfs` carries a row whose
`knowledge_time` is after the spine `decision_time`:

```
Leakage detected in source 'weather.forecasts.gfs': 3 row(s) carry a
knowledge_time after the spine decision_time cutoff 2025-01-02T00:00:00+00:00
(e.g. knowledge_time=2025-01-05T00:00:00+00:00). Fix: filter source
'weather.forecasts.gfs' to knowledge_time <= your decision_time before calling
align(), or call mr.provenance(frame) to inspect the offending rows.
```

Checklist: offending source id ✅ (`weather.forecasts.gfs`); row/entity ✅
(count + `sample_violations` carry `row_idx`); both timestamps ✅
(`knowledge_time` + `decision_time` cutoff); concrete fix ✅ (filter to
`knowledge_time <= decision_time`, or `mr.provenance(frame)`). The clean
co-aligned source is never named.

## Worked example: `ContractError`

A source spec declares a `prefix` that does not end with `_`:

```
Contract violation in source 'weather.obs.iem' (field 'prefix'): prefix must be
a non-empty string ending with '_' (got 'obs'). Fix: set prefix to a column
namespace ending in '_' (e.g. 'obs_').
```

Checklist: offending source id ✅ (`weather.obs.iem`); field/entity ✅ (`prefix`);
timestamps ✅ (vacuous — non-temporal); concrete fix ✅ (the exact value to set).

## Ownership

- **Waves 1-4** write every new error string against this guide, through
`_errmsg`.
- **34-08** runs the final audit: it verifies each user-facing raise routes
through `_errmsg` and carries all four fields. It does not re-author copy.
Loading
Loading