Phase 34: registry API redesign — align/spine/sources + pairs() quickstart + namespace inversion (1.17.0)#123
Merged
Merged
Conversation
…ffolds
- test_align.py: <= as-of boundary, per-entity panel isolation, per-source
leakage attribution (R-22), LeakageError copy carries source id + cutoff +
fix (R-17), align returns a plain stamped DataFrame
- test_align_revisions.py: the falsifier — _collapse_revisions with
revision_order=(('report_type_priority','desc'),) selects final over a later
correction, resolves equal-priority first-seen (STRICT >), and matches
merge_climate byte-for-byte on a shared fixture
- test_parity.py: align-based spike path (CLI label through align selects the
final) + forced-leak error-copy assertion; retains atol=PARITY_ATOL
RED: all three fail with ModuleNotFoundError (mostlyright.align absent).
…NoDataError
- contracts/_source.py: frozen SourceContract (full bitemporal field list);
revision_order=(('report_type_priority','desc'),) + revision_tie='first_seen';
prefix must end '_' (ContractError via _errmsg on violation)
- align.py: mr.align(spine, *sources) FROZEN at two concepts + three pure stages
(_collapse_revisions reproduces merge_climate STRICT> first-seen; equality vs
backward as-of inferred from spine shape; per-source leakage audit R-22 names
the offender via _errmsg R-17). Returns plain DataFrame attrs['source']='align'
- _errmsg.py: leakage_message / contract_message single construction path
- core/exceptions.py: ContractError (CONTRACT_ERROR) + NoDataError (NO_DATA)
GREEN: test_align.py + test_align_revisions.py (9 passed). Falsifier holds —
the contract expresses the climate dedup with NO special case in align().
- research._compose_cli_by_date routes the climate rows through mr.align(spine, (frame, SourceContract)): the collapse stage reproduces the frozen merge_climate dedup via revision_order, an equality join attaches the winning record per settlement day. Replaces the bare last-wins climate_by_date re-key at the _dataset_impl compose site. - Byte-parity preserved: raw_climate is already merged at fetch (collapse idempotent); the winning row is recovered from the ORIGINAL dict by identity (_row_ix), so no DataFrame dtype roundtrip touches cli_* values. - Spine keyed on the climate rows' own station_code (date-only dedup within a single-station call) so the join matches regardless of ICAO K-stripping. Firewall zero-diff (observations.py / live/_sources / observation_type enum / merge_climate untouched); _cache_schema_version unchanged; _obs_aggregates not touched (ULP drift preserved). Fast suite green.
- Enumerates the four required fields every user-facing error carries: offending source id, row/entity, both timestamps where temporal, concrete fix/next call. - Names packages/core/src/mostlyright/_errmsg.py as the single construction path and 34-08 as the final-audit owner (not the author). - Worked LeakageError example (source id + cutoff + fix) and ContractError example (source spec + field to rename).
…tion (RED) - test_registry.py: SOURCE/LABEL kind axis, two-directional .label. invariant, R-09 declared-prefix firewall (obs_ rejected naming firewall; canonical met_ succeeds; firewall list unchanged), idempotent/conflict-checked registration, one-shot ExperimentalFeatureWarning, static-namespace grep, entries() isolation - test_contracts.py: SpineContract fields/frozen/requires-y, SourceContract prefix, validate_source_contract (bitemporal + pit_fidelity), frame validators, TimePoint
…y specs, pit_fidelity refusal, callable provenance verb - test_align_collisions.py: collision -> ContractError (both ids + column + rename), prefix override, no _x/_y, default prefix not forbidden, obs_ feature source refused - test_align_lazy.py: dateless obs() -> LazySourceSpec, dated obs() eager, lazy materialization bounded by spine decision_time, BYO acceptance, latest_only refusal w/ econ pointer, exact/reconstructed pass - test_provenance_verb.py: callable provenance(frame) + per-column valid_from/coverage, align stamps attrs, .history back-compat
…lazy-spec inference + pit_fidelity refusal + callable provenance verb - align.py: LazySourceSpec + _materialize (spine decision_time window inference, R-02) + _apply_prefix (source-id namespacing, R-09 forbidden-prefix firewall, R-24 hard-collision ContractError naming both ids + column + which prefix=, never _x/_y) + _gate_pit_fidelity (R-18 latest_only REFUSE w/ econ.vintages()/ALFRED pointer) + _coverage_edge; stamps df.attrs provenance/coverage; signature frozen at (spine, *sources) - _provenance.py: callable provenance(frame) verb (per-column source id + valid_from/coverage, R-12); history/record/row_window_hash attached as attributes (back-compat) - __init__.py: mostlyright.provenance __getattr__ resolves to the callable verb - weather/obs.py: window-optional obs(station, from_date=None, to_date=None, ...) -> LazySourceSpec when dateless, else byte-stable eager frame (R-02); _obs_source_contract (met_ prefix) + _lazy_obs_spec - kind-aware _apply_prefix byte-passes reconstructed LABELs (CLI parity path) through unchanged - migrate 34-01 test_align.py (obs_->met_ feature prefix) + test_obs_surface.py (start/end->from_date/to_date) test surfaces (R-09/R-02) All ContractError copy via the shared 34-01 _errmsg.contract_message helper (R-17). Four CWOP firewall files + merge_climate zero-diff.
…ine/validators - _contributors.py: additive ContributorSpec.kind field (Literal['source','label'], default 'source' — byte-stable for the four Phase-32 built-ins) + RegistryKind alias - registry/__init__.py: mr.registry.source()/label()/entries()/get(); SOURCE/LABEL kind axis written into the shared _REGISTRY (reuse, no fork), two-directional .label. invariant, SOURCE-only declared-prefix firewall (R-09: reads _FORBIDDEN_LABEL_PREFIXES, rejects obs_ naming the firewall, canonical met_ succeeds, list untouched), reuse _assert_columns_clean, idempotent/conflict-checked writes, one-shot ExperimentalFeatureWarning; plain dict store, NO runtime import-by-id - contracts/_spine.py: SpineContract frozen dataclass (y_columns, decision_time, label_available_time, label source-identity+version) + validate_source_contract (bitemporal + pit_fidelity + prefix) + validate_source_frame / validate_spine_frame - contracts/__init__.py: re-export SpineContract, the validators, TimePoint - test_registry.py: fix _src helper so pit_fidelity override does not collide
…er, usage, empty-state, nearest-match
- deterministic four-tier field weighting (id > tags > columns > description)
- structured filters (domain/kind/availability)
- city= resolver path + cross-venue note; free-text alias extraction (R-05)
- zero-arg full-catalog listing (R-23); empty-state attrs message (R-06)
- resolve_entity('knc') NoDataError subclass with nearest-match 'KNYC' (R-07)
- weather usage names weather.pairs (R-14); ast.parse-runnable + {py,ts} (R-16/R-20)
- no-fuzzy on misspelled token; city= back-compat attrs envelope
…abel.* + markets label axis - test_spine.py: mr.spine() BYO column mapping + ContractError on missing decision_time - test_weather_label_axis.py: weather.days() re-export, two positional dates, tz_override, LIST long-format panel byte-equal to loop, label.cli/daily_extremes y byte-equality, .label. invariant, obs(qc=) - test_weather_forecasts_source.py: forecasts() fcst_* frame, LazySourceSpec, source/model/models threading, align(label,obs,forecasts) fcst_* byte-consistency - test_markets_label_axis.py: kalshi.contracts()/label.settlement(), polymarket.contracts(station_override=), .label. registration
…override homes - mr.spine(df, entity=, decision_time=, y=): BYO bridge into the canonical SpineContract shape (T-34-12 loud reject on missing decision_time; reuses _coerce_date_column) - weather.days(stations, from_date, to_date, *, tz_override=): public bare per-LST-settlement-day spine (re-export from weather/__init__.py; two positional dates; LIST -> long-format panel byte-equal to the loop; tz_override home) - weather.forecasts(entity, from_date=None, to_date=None, *, source/model/models): wrap-and-register bitemporal source over dataset(include_forecast=True); window-optional LazySourceSpec; absorbs the four forecast kwargs - weather.label.cli()/.daily_extremes(): .label.-registered spine factories wrapping the byte-stable recipes (apply_named_recipe / compose_daily_extremes_label) - weather.obs(qc=): qc orphan-kwarg home threaded through the lazy spec builder - four CWOP firewall files zero-diff; climate dedup untouched
…s + station_override home - markets.kalshi.contracts(ticker, from, to): bare spine via parse_ticker station resolution (zero duplicated station logic) - markets.kalshi.label.settlement(): .label.-registered cli-settled spine (wraps dataset(label='cli') oracle) + source-identity attrs - markets.polymarket.contracts(event, from, to, *, station_override=): bare spine; station_override orphan-kwarg home (pins station, bypasses event resolution + network) - markets.polymarket.label.settlement(): .label.-registered daily_extremes-settled spine - PIT-universe responsibility + rolling-features-out-of-scope documented in factory docstrings - [Rule 3] test_registry.py: neutralize the 'weather.label.cli' sample id (now a real import-time registration) to a demo id, avoiding cross-test registry collision
- discover(text?, *, city?, **filters): deterministic field-weighted token
scoring (id > tags > columns > description), NO fuzzy on the local path
- registry/_catalog.py: catalog-as-static-JSON manifest (atomic mkstemp+os.replace,
sort_keys, malformed-tolerant load, env override); language-tagged {py,ts} usage
- registry.register_alias_resolver/resolve_entity + EntityNotFoundError(NoDataError)
with deterministic edit-distance nearest-match over the static registry (R-07)
- free-text alias extraction pairs dataset x entity + keeps cross-venue note (R-05)
- zero-arg full-catalog listing (R-23); empty-but-shaped frame + attrs message (R-06)
- rung-appropriate usage: weather rows name weather.pairs (R-14); pip-install hint on
uninstalled-domain rows (R-16); city= back-compat routes to the resolver path
- discover keeps a stdlib-only import graph (lazy registry import) so a flags-off
import mostlyright never pulls the contracts->satellite chain
… per-domain pairs() - test_shims.py: legacy root symbols importable + DeprecationWarning naming new weather.* home + 2.0 removal + byte-stable delegation; live canonical home; D-22 FutureWarning dead on the bare dataset() shim - test_namespace_inversion.py: exact inverted __all__ (machinery+domains only), root-importable LeakageError/ContractError/NoDataError, NoCWOPDataError re-parented - test_pairs.py: weather.pairs two positional dates + capped + weather.days()/align desugaring docstring + visible graduation error; markets dataset()->pairs() shim
…RED) - 4 cross-SDK JSON fixtures pinning tie-break authority, boundary <=, ns precision, and NaN semantics (single source of truth, both languages read them) - tests/test_align_conformance.py drives the pure join core (RED: driver stub drops joined rows so the semantic assertions fail before wiring) - packages-ts/meta/src/__tests__/alignConformance.test.ts reads the IDENTICAL fixtures; semantic execution deferred to PT-3401 (documented it.skip) - meta vitest include extended to discover src/__tests__ conformance suite
…talog + discover availability filter (GREEN) - tests/test_align_conformance.py drives the pure join core against the four shared fixtures — GREEN (tie-break authority, boundary <=, ns precision, NaN) - scripts/export_schemas.py emits packages-ts/weather/catalog.json from the live Python registry (one-source-of-truth codegen, R-08); byte-deterministic under --check; weather-domain sources are availability=py,ts (R-25 weather-only) - discover() generalized: availability filter reads the committed manifest and surfaces usage.ts, falling back to note/docs — never a bare Python snippet (R-20) - @mostlyrightmd/weather/catalog lazy subpath export (out of the meta bundle) - TS async align() deferred behind parity ticket PT-3401
… kill D-22 flip - Root __all__ = machinery + domains only (align, spine, discover, provenance, Leakage/Contract/NoDataError, weather, markets, registry, contracts, __version__) - research/dataset/Station/CATALOG/StationCatalog/live dropped from __all__ but kept importable via once-per-session DeprecationWarning __getattr__ shims naming the new mostlyright.weather.* home (removed at 2.0); byte-stable same-object delegation - weather/stations.py canonical station home; weather.__init__ re-exports research/dataset/live/stations; live canonical at mostlyright.weather.live - exceptions: LiveStreamError re-parented under NoDataError (additive MRO) so NoLiveDataError/NoCWOPDataError live under NoDataError; three errors root-importable - D-22 label-default FutureWarning removed (the 2.0 flip is cancelled); _D22_BRIDGE_WARNING constant + both warn sites gone; OMITTED label stays 'cli' byte-stable, no warning - align/spine import core.exceptions lazily so the root eager-binds these headline verbs (stable submodule-name shadowing) WITHOUT a bare import pulling core.schemas (satellite covariate) — flags-off import-cost guard stays green - updated pre-existing D-22 / __all__-membership tests to the inverted surface
…() shim train - weather.pairs(entity, from_date, to_date, *, label='cli'): capped (<=5 params), two positional dates, docstring first line names the label='cli' default and the body shows the weather.days(...) -> weather.label.cli(...) -> align(...) desugaring; desugars to align(weather.label.<label>(...), weather.obs(entity)); any unsupported kwarg raises the visible graduation error naming align + the migration guide - markets.kalshi.pairs / markets.polymarket.pairs: renamed the thin dataset delegators to pairs() (entity first param, two positional dates, zero duplicated join logic) - markets.kalshi.dataset / markets.polymarket.dataset: deprecation shims -> pairs() (DeprecationWarning, 2.0 removal); no NEW function named dataset - weather.__init__ exports pairs (label='cli' is explicit/stable, not the D-22 flip)
…catalog entity
- specs/pairs.json: the settlement-join OUTPUT schema pairs()/dataset()/research()
return (v0.14.1 training-pair contract: date, station, cli_*, obs_*, + optional
fcst_*); distinct from the internal settlement-join.json
- _capabilities.py: _SCHEMA_FILES['pairs']='pairs.json'; removed the 'intentionally
omitted' comment + the 'pairs is not supported' load_schema note so load_schema('pairs')
resolves via the normal SPECS_DIR path
- test_capabilities.py: inverted the two intentionally-absent tests
(test_load_schema_pairs_raises_value_error -> _resolves; test_schema_files_excludes_pairs
-> _includes_pairs) so function + schema + catalog cohere
…n/spine The lazy runtime imports (Phase 34 06) made the TYPE_CHECKING ContractError/LeakageError imports dead (no annotations reference them) — remove to clear ruff F401.
… _errmsg - docs/migration/v117-align-spine-sources.md: where-did-each-kwarg-go table (all 26 dataset() kwargs; the 4 forecast kwargs -> weather.forecasts(); label's 5 jobs -> 4 call shapes; 3 orphan kwargs homed), ladder walkthrough, namespace-inversion symbol map, TypeScript-on-1.17 (PT-3401), econ-UNCHANGED (F-2 follow-up), PIT-transform-out-of-scope, D-22-FutureWarning-gone, retrain note - _errmsg.entity_not_found_message: centralize the unknown-entity 'did you mean' wording on the single construction path (R-17 audit fix; byte-identical copy) - registry.EntityNotFoundError routes through the builder; test asserts the suggestion appears and the copy == the builder output - packages-ts/weather/README.md: migration-guide pointer + TS-on-1.17 note Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lease Lockstep minor across all 9 dists (4 PyPI + 5 npm) + uv.lock + CHANGELOG + README, following the 1.16.0 pattern (7d956ba). Phase 34 ships the align/spine/sources ladder + per-domain pairs() as 1.17.0 (operator-signed 2026-07-14; Phase 33 re-sequenced after 34 to 1.18.0). Additive-plus-shims: default research()/dataset() output byte-identical to 1.16; every relocated symbol keeps an importable DeprecationWarning shim; ALL 2.0 removals DOCUMENTED only (nothing removed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
✅ Docs-required check: PASS API-surface change includes docs updates — no reminder needed. API-surface files changed: Docs files changed: |
|
Parity ticket gate: PASSED See |
…y windows + declared-column validation + truthful TS catalog Round-1 three-reviewer findings (codex gpt-5.5 high + Python Architect + TypeScript Architect on PR #123): - CRITICAL align.py — two sources sharing a knowledge_time_col name could hide the second source's leakage audit behind pandas suffixes (equality path) or pollute output columns (as-of path). Each source's temporal keys are now isolated under per-source internal names for its join + audit, then restored when free / dropped when claimed — pandas _x/_y can never appear and a source can never dodge its own audit. Join-key doubles (obs's knowledge_time_col == event_time_col == 'date') keep their name. - CRITICAL align.py — window-less (lazy) sources now materialize over the spine's copy of their event_time_col (the settlement date) instead of decision_time.date(): the 16:30 LST market close crosses UTC midnight at Pacific/Alaska/Hawaii stations, so date-derived windows shifted +1 day and KLAX-class spines silently lost the first settlement day's features. - HIGH align.py — materialized frames are validated against the contract's declared entity/temporal/feature columns (ContractError, style-guide copy); a promised feature column can no longer vanish silently. - HIGH catalog codegen — the exporter now imports weather.label (the manifest previously shipped ONLY weather.forecasts), the py,ts availability upgrade is kind-gated (label recipes stay Python-only, PT-3401), and usage.ts is truthful: a real dataset() snippet for TS source rows, '' + note/docs fallback otherwise — never pairs()/align(). - HIGH enforcement — schema-drift CI diffs packages-ts/weather/catalog.json and triggers on registry/contracts/weather-source paths; a fast-suite test pins the committed manifest bytes to the live registry emission. Fast suite 4607 passed; TS meta 288 passed + weather suite green; size budgets meta 42.41/44 kB. MEDIUM/LOW candidates not blocked on per REVIEW-DISCIPLINE severity gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… + collision-free isolation names + list-entity lazy obs
Iteration-2 findings (codex gpt-5.5 high + Python Architect independently
converged on the flagship-quickstart bug):
- CRITICAL spine.py/obs.py — weather.pairs() returned EVERY obs feature
column silently NaN: the days()-built spine stamped the station AS TYPED
('KNYC') while the obs daily aggregator stamps the normalized registry
code ('NYC'), so align's equality join on (station, date) matched zero
rows. days() now stamps the NORMALIZED code — the v0.14.1 heritage key
the parity fixtures pin ('NYC') and the form research()/dataset() output
and every source frame carries — so all sources join without per-source
restamps. Out-of-registry stations (tz_override path) keep the typed id.
pairs() now has a run-to-completion offline test asserting obs_high_f is
non-NaN; the _fake_dataset byte-oracle stamps normalized codes like the
real dataset().
- HIGH obs.py — list-entity weather.pairs(['KNYC','KLAX'], ...) crashed in
the lazy builder (eager path is single-station: AttributeError 'list'
has no attribute 'strip'). The builder now materializes per station and
concatenates one long-format panel — ONE obs source, met_/obs_ namespace
claimed once.
- HIGH align.py — the temporal-key isolation names (__mr_kt_<id>) are now
guaranteed collision-free via _free_internal_name() (a spine already
carrying __mr_kt_<id> could re-open the pandas-suffix audit-dodge hole).
- pairs() docstring corrected: obs features land as the v0.14.1-heritage
obs_* aggregates (label-adjacent pass-through), not met_*.
TS Architect iteration-2: PASS (no TS files changed since).
Fast suite 4611 passed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Iteration-3 convergent finding (codex + Python Architect found the identical
HIGH; the Architect additionally verified this exact fix as complete):
- HIGH kalshi.py/_spine_days (reused by polymarket contracts/label) stamped
the station AS TYPED (ICAO 'KNYC' from parse_ticker), while weather.obs()
frames carry the normalized registry code ('NYC') — the same all-NaN
equality-join mismatch iter-2 fixed for weather.days(), left unfixed in
the markets domain: align(kalshi.label.settlement(ticker, d1, d2),
weather.obs('KNYC')) matched zero rows. The join-key convention now lives
ONCE in mostlyright._internal._pairs.station_key() (normalized code,
as-typed fallback for out-of-registry ids); weather.days() delegates to
it and markets _spine_days stamps it (market_close_utc still receives the
raw id — both forms resolve identically). Markets label-axis tests updated
to the normalized codes.
Reviewer state after this commit: codex iter-3 1 HIGH -> fixed here;
Python Architect iter-3 1 HIGH (same, fix pre-verified) -> fixed here, all
other sweeps clean (intl/tz_override loud-fail consistency, list-entity,
firewall zero-diff); TS Architect PASS (iter-2, no TS changes since).
Fast suite 4611 passed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 34 replaces the 26-kwarg
dataset()composer with a composable three-concept surface —mr.align(spine, *sources), spine constructors, and registered sources — plus a per-domainpairs()quickstart, cross-domaindiscover(), a namespace inversion with a full deprecation shim train, and the v1.17 migration guide. Ships as 1.17.0, bumped in lockstep across all 9 dists (4 PyPI + 5 npm). Additive-plus-shims: no symbol is removed this release; 2.0 removals are documented only.Per the pre-signed operator decision (34-08 Task 2): Phase 34 takes 1.17.0, and Phase 33 (
reconcile()) is re-sequenced after 34 as 1.18.0, re-scoped to consumealign()'s provenance.The 8 plans
align()pure-join core +SourceContract+ContractError/NoDataError; CLI settlement label composed throughalign()(Path A falsifier held);docs/error-copy-style-guide.md+ shared_errmsgbuilder.label.invariant +mr.contractsspine/validators;met_prefix firewall reconciliationobs(), lazy-spec inference,pit_fidelityrefusal, callable provenance verbmr.spine()+weather.days()/forecasts()/label.*+ markets spine constructors;station_override/qc/tz_overridehomesdiscover()catalog search: token scoring, alias resolver, language-tagged{py, ts}usage, empty-state + nearest-match__all__flip, legacy shims, error-taxonomy collapse, D-22 default-flip removed, per-domainpairs()quickstart +dataset()→pairs()shim trainalign()conformance fixtures (Python HARD gate, TS reads identical JSON), registry-generatedpackages-ts/weather/catalog.jsoncodegen (export_schemas.py --checkbyte-deterministic), TSdiscover()availability filter, lazy@mostlyrightmd/weather/catalogsubpathdocs/migration/v117-align-spine-sources.md(26-kwarg where-did-each-go table, label 5-jobs→4-shapes, TypeScript-on-1.17, econ-unchanged F-2 follow-up, PIT-transform out-of-scope), error-copy conformance audit across 34-01..07, parity ticket PT-3401 filed, lockstep 1.17.0 bump + release gateRelease-gate results
uv run pytest -m "not live" -q)scripts/check_no_hosted_calls.py)CI=1 pnpm -r run test)pnpm run size, TS-BUNDLE-01)_cache_schema_versionvsmainpytest tests/test_parity.py -m live -q)Cases 1 (KNYC), 2 (KMDW), 5 (KMSY) are byte + dtype green. Cases 3 (KLAX) and 4 (KMIA) fail exclusively on
obs_mean_f(column index 7 — the observation sum-then-divide mean): KLAX56.7826live vs56.7400fixture, KMIA73.8172vs73.7235.This is environmental fixture drift, not a regression:
cli_*label columns (indices 0–6) match byte-for-byte on every case.climate.pyare zero-diff vsmain; the phase'sresearch.pydiff touches only thecli_*attachment path (_compose_cli_by_date), never_obs_aggregates/obs_*.34-01-SUMMARY.mdroot-caused the identical KLAX signature (same value pair) at phase start, on code the phase does not touch.mostlyright==0.14.1is impossible:api.mostlyright.mdwas re-pointed in Phase 28, so the May-2026 fixtures are the last ground truth.Options: accept the drift for the release (label columns — the settlement-critical surface — are byte-exact), pin the parity gate to a cached obs snapshot, or add a documented tolerance for
obs_*sum-aggregates on known-drifted days. No tag or publish happens until this is resolved.TS Parity
TS users keep
research()/dataset()unchanged on 1.17.align/spine/pairsare Python-only, deferred behind parity ticket PT-3401 (.planning/parity-tickets/PT-3401-ts-async-align.md) which also records the availability partial-parity policy, the py-only typed-error criterion (R-21), and the markets/econ/meta catalog-subpath follow-up (R-25). The npm weather README points at the migration guide.Data licensing
No new data source, fetcher, or cache persistence — API-surface redesign over existing sources only.
Planning
Plan + summaries:
mostlyrightmd/planning→phases/34-registry-api-redesign-align-spine-sources-composition-namesp/(SUMMARYs 34-01..34-08; UX review record in34-REVIEW.md).🤖 Generated with Claude Code