From c906e8266936fbaa43a0a3bc249b4ae7e820ab17 Mon Sep 17 00:00:00 2001 From: hobbyistz <293619914+hobbyistz@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:04:40 -0700 Subject: [PATCH] feat: re-derive as-published history by default Re-derive each as-published observation under its effective methodology, using that version's own lookback history and reporting its version and methodology identity. Label observations before the effective time back-calculated. Restore full reproduction as the default, retain receipts as a fast opt-in, and preserve explicit version selection. Share the disclosure-bound calculation and discover younger histories from their available origin. Exercise version transitions, pre-launch selection, and 91- and 99-day histories; run daily full reproduction for all four public SKUs. Signed-off-by: hobbyistz <293619914+hobbyistz@users.noreply.github.com> --- CHANGELOG.md | 4 + README.md | 78 +++--- reproduce | 24 +- scripts/verify_published_record.py | 18 +- src/gpu_index/published/full.py | 132 ++++++++-- src/gpu_index/published/verify.py | 10 +- tests/live/test_full_reproduction_live.py | 13 +- tests/unit/test_published_full.py | 300 ++++++++++++++++++++++ tests/unit/test_published_reader_cli.py | 9 - tests/unit/test_reproduce_routing.py | 6 +- 10 files changed, 492 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da7df7a..c3b7311 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ their own keyspaces. Newest first. ## 2026-09-08 +`./reproduce` now re-derives the as-published history end to end under the version +live at each stamp, using each version's own lookback history. `--receipts` offers +the fast per-observation check, and `--version` selects a single version. + Published history now serves the version that was live at each observation. A methodology version's keyspace holds that version's full re-derivation from its first observation, including rows before its effective time, for verification; diff --git a/README.md b/README.md index f8bdba0..637f517 100644 --- a/README.md +++ b/README.md @@ -64,54 +64,64 @@ pip install -e . install local; on systems that allow bare `pip install`, the venv lines are optional.) -That verifies the current UTC day's published receipts and artifact digests. -By default it reads the as-published keyspace advertised by `latest.json` as -`data.versions[].history_path`. Until that field is available, it prints a -notice and falls back to `current_version`. Each result names the version -and `methodology_id` verified. - -Exit 0: every verifiable value matched; withheld receipts degrade to digest-only -verification with a notice. Exit 1 means a mismatch, invalid artifact, or digest -failure; exit 2 means verification could not run or the arguments were invalid. -To check another accelerator or time, run +That re-derives the current UTC day's as-published index end to end, including +attendance events and factors, liveness scores, weights, votes, IQM, value, and +stability band. It reads the history advertised by `latest.json` as +`data.versions[].history_path`, selects the version live at each stamp by its +succession `effective_from`, and uses that version's own `v/` lookback history. +Before public launch, it selects the launch version. Each result reports the +version and `methodology_id`; observations before that version's effective time +are labeled `back-calculated`. + +The raw inputs are disclosed prices and dispersions, recorded currency and FX, +upstream status, carry basis, filter verdicts, timing, top-level flags, and +`calc_params`. Published attendance factors, liveness scores, and weights are +comparison outputs, never derivation inputs. Every artifact read is digest +verified. Missing required history or withheld raw inputs cause a full-reproduction +refusal. If `history_path` is absent, the command prints a notice and uses +`current_version` (or the legacy flat record when no version pointer exists). + +Exit 0: every verifiable value matched. Exit 1 means a mismatch, invalid artifact, +or digest failure; exit 2 means verification could not run or the arguments were +invalid. To check another accelerator or time, run `./reproduce YYYY-MM-DD`; add `THH` to check one UTC -hour. `--receipts` explicitly selects this default receipts check. +hour. A full day takes a few minutes. -To verify a particular version's re-derivation, pass `--version `: +`--full` explicitly selects the default. `--receipts` is the fast opt-in: it +recomputes each value and band from that observation's own published receipts, +without re-deriving attendance or weights. In receipts mode only, withheld +contributing receipts degrade to digest-only verification with a notice. + +To verify one version's re-derivation throughout, pass `--version `: ``` +./reproduce --version 5 h100 2026-09-01 +./reproduce --version 6 h100 2026-09-01 ./reproduce --receipts --version 5 h100 2026-09-01 -./reproduce --receipts --version 6 h100 2026-09-01 -./reproduce --full --version 5 h100 2026-09-01 ``` -Explicit-version observations before that version's `effective_from` are labeled -`back-calculated`. `--full` derives attendance events and factors, liveness -scores, weights, votes, and values from raw disclosed history without consuming -published derived intermediates. It stays within one version and takes a few -minutes; pass `--version `. During the cutover, `--full` without a version -retains the current-version fallback only when `history_path` is absent. +Rows before the selected version's `effective_from` are labeled +`back-calculated` in both modes. -Recorded 2026-09-01 under version 5; reproduce it with -`./reproduce --full --version 5 h100 2026-09-01`. Taken live against -https://data.getcomputable.com at 07:29 UTC and left exactly as recorded; the -repeated middle lines and each observation's derived 16-source weight vector -are elided. +Recorded 2026-09-08 against https://data.getcomputable.com from a checkout with +no `./data`. This day spans two versions: 76 observations under version 5 and +20 under version 6. Repeated lines and derived source weight vectors are elided. ``` -$ ./reproduce h100 "$(date -u +%F)" +$ ./reproduce h100 2026-09-03 published record: full history via public HTTPS front https://data.getcomputable.com raw-only full reproduction: prices, dispersions, upstream status, carry basis, filter verdicts, timing, top-level flags, and calc_params are inputs; published derived intermediates are not -H100 2026-09-01T00 derived 3.456577 (band 0.556277) published 3.456577 (band 0.556277) MATCH public digests OK -H100 2026-09-01T00:15 derived 3.457619 (band 0.557319) published 3.457619 (band 0.557319) MATCH public digests OK -[... 26 more MATCH lines, each followed by its derived weight vector ...] -H100 2026-09-01T07 derived 3.465561 (band 0.565261) published 3.465561 (band 0.565261) MATCH public digests OK -H100 2026-09-01T07:15 derived 3.465393 (band 0.565093) published 3.465393 (band 0.565093) MATCH public digests OK -summary: 30 observation(s): 30 MATCH, 0 MISMATCH +H100 2026-09-03T00 derived 3.519725 (band 0.520902) published 3.519725 (band 0.520902) MATCH public digests OK version 5 methodology_id h100_sxm_v1_calc_v8 +H100 2026-09-03T00:15 derived 3.519749 (band 0.520978) published 3.519749 (band 0.520978) MATCH public digests OK version 5 methodology_id h100_sxm_v1_calc_v8 +[... 73 more MATCH lines under version 5 ...] +H100 2026-09-03T18:45 derived 3.505789 (band 0.605489) published 3.505789 (band 0.605489) MATCH public digests OK version 5 methodology_id h100_sxm_v1_calc_v8 +H100 2026-09-03T19 derived 3.508224 (band 0.518224) published 3.508224 (band 0.518224) MATCH public digests OK version 6 methodology_id h100_sxm_v1_calc_v10 +[... 18 more MATCH lines under version 6 ...] +H100 2026-09-03T23:45 derived 3.547149 (band 0.646849) published 3.547149 (band 0.646849) MATCH public digests OK version 6 methodology_id h100_sxm_v1_calc_v10 +summary: 96 observation(s): 96 MATCH, 0 MISMATCH, 0 degraded ``` -Run `./reproduce --receipts` for the receipts-only value check; `--producer` -and `--lane` replay a local collection record rather than the published one. +`--producer` and `--lane` replay a local collection record. To compare the latest print with prices visible at its sources now, run `./reproduce --collect `. It reports `SAME`, `MOVED`, diff --git a/reproduce b/reproduce index 76926a3..3fd08d4 100755 --- a/reproduce +++ b/reproduce @@ -15,18 +15,19 @@ # files under observations/YYYY/MM/DD.json) -- a recompute-and-match run # (scripts/verify_published_record.py): every file's envelope digest is # verified, and each observation's index value and stability band are -# recomputed from the published receipts and matched exactly. The record +# re-derived from raw history and matched exactly. The record # is read from a local downloaded copy under GPU_INDEX_DATA_DIR (default # ./data) when it holds the requested day; otherwise from the public # HTTPS front -- GPU_INDEX_PUBLIC_BASE_URL when set, defaulting to the # official record host https://data.getcomputable.com. Exit 0 all-match / # 1 any mismatch or digest FAIL / 2 could not verify (front unreachable, # or no published artifacts for the date) -- the default NEVER exits 0 -# without verifying something; withheld sources degrade an observation -# to digest-only with a distinct message. +# without verifying something. --receipts is the fast opt-in; withheld +# contributing sources degrade that mode to digest-only with a message. # -# --full --version replays one version's full public -# calculation from raw disclosed inputs: +# --full (default) replays each stamp under its effective version, using +# that version's own history. --version selects one version throughout. +# The calculation uses raw disclosed inputs: # per-source prices and dispersions, status/timing facts, filter verdicts, # carried basis, and published calculation parameters. It derives the # attendance events and factors, liveness scores, weight vector, votes, @@ -57,7 +58,7 @@ usage: reproduce reproduce --frozen public SKU. Verifies the as-published record from - its receipts when latest.json advertises history_path; + raw history when latest.json advertises history_path; otherwise prints a notice and uses current_version. Reads a local copy under GPU_INDEX_DATA_DIR when it holds the requested day, else the public front @@ -66,9 +67,9 @@ usage: reproduce verifiable value matched; 1 mismatch/digest FAIL; 2 could not verify --version verify this version's full re-derivation keyspace - --full derive weights and values from raw history; pass - --version (a day takes a few minutes) - --receipts the default fast check: recompute each value from its own + --full default: derive weights and values from raw history + under the version live at each stamp (takes a few minutes) + --receipts fast opt-in: recompute each value from its own published receipts only (no weight re-derivation) --collect collect current source prices for the latest print and compare them with its receipts. Past source @@ -85,9 +86,8 @@ EOF MODE=sku PRODUCER=0 -# Receipt verification can follow a mixed-methodology as-published day. -# Raw full re-derivation remains an explicit per-version operation. -FULL=0 +# Re-derive the as-published series with each stamp's effective version. +FULL=1 FULL_EXPLICIT=0 COLLECT=0 VERSION="" diff --git a/scripts/verify_published_record.py b/scripts/verify_published_record.py index b29fd1f..fd37b64 100644 --- a/scripts/verify_published_record.py +++ b/scripts/verify_published_record.py @@ -58,6 +58,7 @@ FullReproductionRefusal, read_full_history, reproduce_full_history, + reproduce_published_history, ) from gpu_index.published.reader import PublishedRecordReader # noqa: E402 from gpu_index.published.verify import ( # noqa: E402 @@ -148,8 +149,11 @@ def _run_full( "calc_params are inputs; published derived intermediates are not" ) try: - history = read_full_history(reader, sku=sku, target_date=date, version=version) - run = reproduce_full_history(history, target_date=date) + if version is None and pointer and "history_path" in pointer: + run = reproduce_published_history(reader, sku=sku, target_date=date) + else: + history = read_full_history(reader, sku=sku, target_date=date, version=version) + run = reproduce_full_history(history, target_date=date) except FullReproductionRefusal as exc: print(f"FULL REFUSAL [{exc.code}]: {exc}", file=sys.stderr) return 2 @@ -180,7 +184,6 @@ def _run_full( ) return 2 - identities = {row["observed_at"]: row for row in history if row["sku"] == sku} matched = mismatched = 0 for check in checks: if check.verdict == VERDICT_MATCH: @@ -189,8 +192,8 @@ def _run_full( mismatched += 1 verdict = "MATCH" if check.verdict == VERDICT_MATCH else "MISMATCH" identity = _identity_label( - identities[check.observed_at], pointer, - explicit_version=version if explicit else None, + {"observed_at": check.observed_at, "methodology_id": check.methodology_id}, + pointer, explicit_version=check.version or (version if explicit else None), ) print( f"{check.sku} {_stamp_label(check.observed_at)} " @@ -250,7 +253,7 @@ def main(argv=None) -> int: ) parser.add_argument( "--version", type=_positive_version, - help="verify this integer version's re-derivation (required for --full)", + help="verify this integer version's re-derivation", ) args = parser.parse_args(argv) @@ -276,9 +279,6 @@ def main(argv=None) -> int: try: pointer = reader.version_pointer(sku) version = args.version - if args.full and version is None and pointer and "history_path" in pointer: - print("--full re-derives one version; pass --version ", file=sys.stderr) - return 2 if version is None and (pointer is None or "history_path" not in pointer): version = pointer["current_version"] if pointer else None target = f"current_version {version}" if pointer else "legacy flat keyspace" diff --git a/src/gpu_index/published/full.py b/src/gpu_index/published/full.py index 3b47091..8610c95 100644 --- a/src/gpu_index/published/full.py +++ b/src/gpu_index/published/full.py @@ -6,8 +6,8 @@ import math from bisect import bisect_left -from dataclasses import dataclass -from datetime import date, timedelta +from dataclasses import dataclass, replace +from datetime import date, datetime, timedelta from typing import Any, Dict, Iterable, Optional, Tuple from gpu_index.index.panel import ( @@ -22,10 +22,13 @@ new_weight_state, ) from gpu_index.published.artifacts import PublishedRecordError +from gpu_index.published.verify import MIN_DISCLOSURE_WINDOW_DAYS, _history_bound_days VERDICT_MATCH = "match" VERDICT_MISMATCH = "mismatch" -FULL_HISTORY_BOUND_DAYS = 100 +FULL_HISTORY_BOUND_DAYS = MIN_DISCLOSURE_WINDOW_DAYS +# The launch-era methodology also covers observations before public launch. +_PUBLIC_LAUNCH = datetime.fromisoformat("2026-09-01T00:13:39Z") class FullReproductionRefusal(PublishedRecordError): @@ -55,6 +58,8 @@ class FullObservationCheck: published_band: Optional[float] derived_weights: Dict[str, float] first_divergence: Optional[FullDivergence] = None + methodology_id: str = "" + version: int | None = None @dataclass(frozen=True) @@ -270,10 +275,9 @@ def read_full_history( """Read the contiguous public history required by the weighting engine. The public 90-day series identifies the observable record origin. If a - day exists immediately before that rolling window, the lane is older and - the full 100-day disclosure bound is required. If it does not, the series - begins at the lane's public corpus origin and replay starts from the - engine's empty genesis state. + day exists immediately before that rolling window, walk backward until + the disclosure bound or the first unavailable day. A younger version + starts at its public corpus origin, from the engine's empty genesis state. """ # One version for the series, origin probe and every history day. version_args = {"version": version} if version is not None else {} @@ -299,24 +303,38 @@ def read_full_history( f"the public 90d series begins at {series_start}, after {target}", ) - previous = reader.read_day( - (series_start - timedelta(days=1)).isoformat(), sku=sku, **version_args + target_day = reader.read_day(target_date, sku=sku, **version_args) + target_rows = (target_day or {}).get("data", {}).get("observations", []) + bound_days = max( + (_history_bound_days( + history_days=row["calc_params"]["liveness"]["history_days"], + forward_horizons_hours=row["calc_params"]["liveness"]["forward_horizons_hours"], + ) for row in target_rows if row.get("calc_params", {}).get("liveness")), + default=FULL_HISTORY_BOUND_DAYS, + ) + bound_start = target - timedelta(days=bound_days - 1) + start = max(series_start, bound_start) + cached_days = {target: target_day} + # Walk back from an observable day: a young series may contain 91-99 + # days even though the full disclosure-bound day does not exist yet. + while start > bound_start: + previous_date = start - timedelta(days=1) + previous = reader.read_day(previous_date.isoformat(), sku=sku, **version_args) + if previous is None: + break + cached_days[previous_date] = previous + start = previous_date + bound_label = ( + f"{bound_days}-day history bound beginning {start.isoformat()}" + if start == bound_start + else f"public corpus origin {start.isoformat()}" ) - if previous is None: - start = series_start - bound_label = f"public corpus origin {series_start.isoformat()}" - else: - start = target - timedelta(days=FULL_HISTORY_BOUND_DAYS - 1) - bound_label = ( - f"{FULL_HISTORY_BOUND_DAYS}-day history bound beginning " - f"{start.isoformat()}" - ) observations = [] cursor = start while cursor <= target: day = cursor.isoformat() - envelope = previous if cursor == series_start - timedelta(days=1) else None + envelope = cached_days.get(cursor) if envelope is None: envelope = reader.read_day(day, sku=sku, **version_args) if envelope is None: @@ -340,7 +358,7 @@ def read_full_history( "insufficient_observable_history", "the public 90d series does not expose its observation lattice", ) - lower = series_start.isoformat() + lower = max(series_start, start).isoformat() upper = target.isoformat() expected = sorted( str(row.get("observed_at")) @@ -373,9 +391,14 @@ def read_full_history( def reproduce_full_history( - observations: Iterable[dict], *, target_date: str + observations: Iterable[dict], *, target_date: str, + comparison_rows: dict[str, dict] | None = None, ) -> FullReproduction: - """Derive target-day weights, votes, IQM, and index from raw public rows.""" + """Derive target-day weights, votes, IQM, and index from raw public rows. + + Optional comparison rows supply published outputs only; every derivation + input and state transition still comes from the version history. + """ history = sorted(list(observations), key=_stamp) schedule = _ObservedSchedule(history) state = new_weight_state() @@ -495,10 +518,14 @@ def reproduce_full_history( None if composite is None else composite["confidence_usd_gpu_hr"] ) if observation_date == target_date: - published_value = observation.get("value_usd_gpu_hr") - published_band = observation.get("stability_band_usd_gpu_hr") + comparison = ( + observation if comparison_rows is None + else comparison_rows.get(observation["observed_at"], observation) + ) + published_value = comparison.get("value_usd_gpu_hr") + published_band = comparison.get("stability_band_usd_gpu_hr") divergence = _first_divergence( - receipts, + comparison["receipts"], block, derived_weights, derived_value=derived_value, @@ -521,6 +548,7 @@ def reproduce_full_history( published_band=published_band, derived_weights=derived_weights, first_divergence=divergence, + methodology_id=str(observation.get("methodology_id", "")), ) ) @@ -560,3 +588,57 @@ def reproduce_full_history( f"the public history contains no observation for {target_date}", ) return FullReproduction(checks=tuple(checks)) + + +def reproduce_published_history( + reader: Any, *, sku: str, target_date: str, +) -> FullReproduction: + """Re-derive each as-published stamp within its effective version's history.""" + pointer = reader.version_pointer(sku) + if pointer is None or "history_path" not in pointer: + raise FullReproductionRefusal( + "history_path_unavailable", + f"latest.json does not advertise as-published history for {sku}", + ) + envelope = reader.read_day(target_date, sku=sku) + rows = (envelope or {}).get("data", {}).get("observations", []) + if not rows: + raise FullReproductionRefusal( + "target_not_observable", f"no {sku} observations for {target_date}" + ) + succession = sorted( + pointer["succession"], key=lambda entry: datetime.fromisoformat(entry["effective_from"]) + ) + groups: dict[int, dict[str, dict]] = {} + for row in rows: + stamp = row["observed_at"] + at = max(datetime.fromisoformat(stamp), _PUBLIC_LAUNCH) + live = [entry for entry in succession + if datetime.fromisoformat(entry["effective_from"]) <= at] + if not live: + raise FullReproductionRefusal( + "version_unavailable", f"{stamp}: no effective version is advertised" + ) + selected = live[-1] + if row["sku"] != sku or row["methodology_id"] != selected["methodology_id"]: + raise PublishedRecordError( + f"{stamp}: as-published row disagrees with effective version " + f"{selected['version']} methodology_id {selected['methodology_id']}" + ) + targets = groups.setdefault(selected["version"], {}) + if stamp in targets: + raise PublishedRecordError(f"{stamp}: repeated as-published observation") + targets[stamp] = row + + checks = [] + for version, targets in groups.items(): + history = read_full_history(reader, sku=sku, target_date=target_date, version=version) + run = reproduce_full_history(history, target_date=target_date, comparison_rows=targets) + selected_checks = [check for check in run.checks if check.observed_at in targets] + if sorted(check.observed_at for check in selected_checks) != sorted(targets): + raise FullReproductionRefusal( + "target_not_observable", + f"version {version}: history does not contain every as-published stamp", + ) + checks.extend(replace(check, version=version) for check in selected_checks) + return FullReproduction(checks=tuple(sorted(checks, key=lambda check: check.observed_at))) diff --git a/src/gpu_index/published/verify.py b/src/gpu_index/published/verify.py index 0e8e1a5..553b710 100644 --- a/src/gpu_index/published/verify.py +++ b/src/gpu_index/published/verify.py @@ -57,7 +57,7 @@ import math from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, Iterable, List, Optional, Tuple from gpu_index.common.jsondiff import field_diffs from gpu_index.index.panel import median_stddev_composite @@ -87,7 +87,13 @@ class UnsupportedStatisticError(PublishedRecordError): # consumes only each observation's own receipts, which embed the # liveness weights as published. What a shorter window costs is the # ability to re-derive the weight vector itself from the public record. -MIN_DISCLOSURE_WINDOW_DAYS = 100 # 90d lookback + 2d forward + slack +def _history_bound_days( + *, history_days: int = 90, forward_horizons_hours: Iterable[float] = (6, 24, 48), +) -> int: + return int(history_days) + math.ceil(max(forward_horizons_hours) / 24) + 8 + + +MIN_DISCLOSURE_WINDOW_DAYS = _history_bound_days() def disclosure_window_warning( diff --git a/tests/live/test_full_reproduction_live.py b/tests/live/test_full_reproduction_live.py index 01c51c8..61f32a5 100644 --- a/tests/live/test_full_reproduction_live.py +++ b/tests/live/test_full_reproduction_live.py @@ -12,8 +12,7 @@ from gpu_index.common.bucket import BucketConfig from gpu_index.published.full import ( VERDICT_MATCH, - read_full_history, - reproduce_full_history, + reproduce_published_history, ) from gpu_index.published.reader import PublishedRecordReader @@ -22,7 +21,8 @@ DEFAULT_PUBLIC_BASE_URL = "https://data.getcomputable.com" -def test_current_h100_reproduces_from_raw_public_history_only(): +@pytest.mark.parametrize("sku", ["H100", "H200", "B200", "B300"]) +def test_current_as_published_history_reproduces_from_raw_inputs(sku): public_url = ( os.environ.get("GPU_INDEX_PUBLIC_BASE_URL") or DEFAULT_PUBLIC_BASE_URL ) @@ -31,10 +31,7 @@ def test_current_h100_reproduces_from_raw_public_history_only(): ) today = datetime.datetime.now(datetime.timezone.utc).date().isoformat() - pointer = reader.version_pointer("H100") - version = pointer["current_version"] if pointer else None - history = read_full_history(reader, sku="H100", target_date=today, version=version) - run = reproduce_full_history(history, target_date=today) + run = reproduce_published_history(reader, sku=sku, target_date=today) - assert run.checks, f"the public record has no H100 observations for {today}" + assert run.checks, f"the public record has no {sku} observations for {today}" assert all(check.verdict == VERDICT_MATCH for check in run.checks) diff --git a/tests/unit/test_published_full.py b/tests/unit/test_published_full.py index 96dcadb..fa4d4d4 100644 --- a/tests/unit/test_published_full.py +++ b/tests/unit/test_published_full.py @@ -6,7 +6,11 @@ import copy import importlib.util +import json +import os +import subprocess import sys +from datetime import date, timedelta from pathlib import Path import pytest @@ -19,6 +23,7 @@ public_weight_print, read_full_history, reproduce_full_history, + reproduce_published_history, ) REPO_ROOT = Path(__file__).resolve().parents[2] @@ -71,6 +76,7 @@ def _observation(): { "source_id": f"s{i}", "upstream_status": "ok", + "status": "ok", "carry_basis": None, "filter_verdict": "accepted", "price_disclosure": "published", @@ -377,3 +383,297 @@ def read_day(self, date, *, sku, **kwargs): "FIRST DIVERGENCE: 2026-09-01T00:00:00.000Z s1 weight " "derived 0.2 published 999.0" ) in output + + +@pytest.mark.parametrize("age", [91, 99, 100, 110]) +@pytest.mark.parametrize("history_days", [90, 120]) +def test_history_loader_reaches_available_origin_or_full_bound(age, history_days): + from gpu_index.published.verify import _history_bound_days + + bound = _history_bound_days(history_days=history_days, forward_horizons_hours=[48]) + + target = date(2026, 9, 1) + origin = target - timedelta(days=age - 1) + series_start = target - timedelta(days=89) + days = { + (origin + timedelta(days=i)).isoformat(): { + "observed_at": f"{origin + timedelta(days=i)}T00:00:00.000Z", + "calc_params": {"liveness": {"history_days": history_days, + "forward_horizons_hours": [48]}}, + } + for i in range(age) + } + reads = [] + + class Reader: + def read_series(self, _range, *, sku, version): + assert (sku, version) == ("H100", 5) + return { + "meta": {"from_observed_at": days[series_start.isoformat()]["observed_at"]}, + "data": {"observations": [row for day, row in days.items() + if day >= series_start.isoformat()]}, + } + + def read_day(self, day, *, sku, version): + assert (sku, version) == ("H100", 5) + reads.append(day) + return {"data": {"observations": [days[day]]}} if day in days else None + + history = read_full_history(Reader(), sku="H100", target_date=str(target), version=5) + assert len(history) == min(age, bound) + assert history[0]["observed_at"] == ( + f"{max(origin, target - timedelta(days=bound - 1))}T00:00:00.000Z" + ) + assert len(reads) == len(set(reads)) + + +def _two_version_reader(): + # Version 2's own earlier raw price is needed for its carried target. + # Mixing the as-published lookback would produce 3.0 instead of 6.0. + early = _observation() + early["observed_at"] = "2026-09-03T18:45:00.000Z" + later = copy.deepcopy(early) + later["methodology_id"] = "h100_sxm_v1_calc_v10" + later["value_usd_gpu_hr"] = 6.0 + later["stability_band_usd_gpu_hr"] = 2.2 + for receipt in later["receipts"]: + receipt["price"] *= 2 + receipt["sd"] *= 2 + carried = copy.deepcopy(later) + carried["observed_at"] = "2026-09-03T19:00:00.000Z" + for receipt in carried["receipts"]: + receipt["upstream_status"] = "carried" + earlier_end = copy.deepcopy(early) + earlier_end["observed_at"] = carried["observed_at"] + + class Reader: + def __init__(self): + self.published = [early, carried] + self.histories = {1: [early, earlier_end], 2: [later, carried]} + self.read_versions = [] + self.pointer = { + "current_version": 2, "history_path": "H100/published", + "succession": [ + {"version": 1, "methodology_id": early["methodology_id"], + "effective_from": "2026-09-01T00:13:39Z"}, + {"version": 2, "methodology_id": later["methodology_id"], + "effective_from": "2026-09-03T18:59:44Z"}, + ], + } + + def describe(self): + return "synthetic public record" + + def version_pointer(self, sku): + return self.pointer + + def read_series(self, _range, *, sku, version): + self.read_versions.append(version) + rows = self.histories[version] + return {"meta": {"from_observed_at": rows[0]["observed_at"]}, + "data": {"observations": rows}} + + def read_day(self, day, *, sku, version=None): + rows = self.published if version is None else self.histories[version] + matching = [row for row in rows if row["observed_at"][:10] == day] + return {"data": {"observations": matching}} if matching else None + + return Reader() + + +def test_as_published_full_uses_each_versions_own_raw_history(): + reader = _two_version_reader() + result = reproduce_published_history(reader, sku="H100", target_date="2026-09-03") + assert [(check.verdict, check.derived_value, check.version, check.methodology_id) + for check in result.checks] == [ + (VERDICT_MATCH, 3.0, 1, "h100_sxm_v1_calc_v8"), + (VERDICT_MATCH, 6.0, 2, "h100_sxm_v1_calc_v10"), + ] + assert reader.read_versions == [1, 2] + + +def test_as_published_full_cli_reports_both_versions(monkeypatch, capsys): + reader = _two_version_reader() + spec = importlib.util.spec_from_file_location( + "as_published_cli", REPO_ROOT / "scripts" / "verify_published_record.py" + ) + cli = importlib.util.module_from_spec(spec) + spec.loader.exec_module(cli) + monkeypatch.setattr(cli, "PublishedRecordReader", lambda: reader) + assert cli.main(["--sku", "H100", "--date", "2026-09-03", "--full"]) == 0 + output = capsys.readouterr().out + assert "version 1 methodology_id h100_sxm_v1_calc_v8" in output + assert "version 2 methodology_id h100_sxm_v1_calc_v10" in output + assert "2 MATCH, 0 MISMATCH, 0 degraded" in output + assert "NOTICE" not in output + + +def test_pre_launch_history_selects_launch_version_and_labels_rows(monkeypatch, capsys): + reader = _two_version_reader() + # Advertise an earlier version as well: pre-launch means launch version, + # not the oldest advertised version or the version effective on that date. + reader.histories = {version + 1: rows for version, rows in reader.histories.items()} + reader.pointer["current_version"] += 1 + for entry in reader.pointer["succession"]: + entry["version"] += 1 + reader.pointer["succession"].insert(0, { + "version": 1, "methodology_id": "earlier_method", + "effective_from": "2026-08-01T00:00:00Z", + }) + before = copy.deepcopy(reader.histories[2][0]) + before["observed_at"] = "2026-08-25T00:00:00.000Z" + reader.published = [before] + reader.histories[2] = [before] + spec = importlib.util.spec_from_file_location( + "pre_launch_cli", REPO_ROOT / "scripts" / "verify_published_record.py" + ) + cli = importlib.util.module_from_spec(spec) + spec.loader.exec_module(cli) + monkeypatch.setattr(cli, "PublishedRecordReader", lambda: reader) + assert cli.main(["--sku", "H100", "--date", "2026-08-25", "--full"]) == 0 + assert reader.read_versions == [2] + assert ("version 2 methodology_id h100_sxm_v1_calc_v8 back-calculated" + in capsys.readouterr().out) + + +@pytest.mark.parametrize("field", ["value_usd_gpu_hr", "stability_band_usd_gpu_hr", + "weight", "liveness_score", "attendance_factor"]) +def test_full_compares_against_as_published_outputs(field): + reader = _two_version_reader() + reader.published = copy.deepcopy(reader.published) + if field in reader.published[1]: + reader.published[1][field] = 999.0 + else: + reader.published[1]["receipts"][0][field] = 999.0 + result = reproduce_published_history(reader, sku="H100", target_date="2026-09-03") + assert result.checks[0].verdict == VERDICT_MATCH + assert result.checks[1].verdict == "mismatch" + assert result.checks[1].derived_value == 6.0 + + +def test_as_published_methodology_must_be_effective_at_stamp(): + reader = _two_version_reader() + reader.published = copy.deepcopy(reader.published) + reader.published[1]["methodology_id"] = reader.published[0]["methodology_id"] + with pytest.raises(ValueError, match="disagrees with effective version 2"): + reproduce_published_history(reader, sku="H100", target_date="2026-09-03") + + +def test_as_published_target_must_exist_in_version_history(): + reader = _two_version_reader() + reader.histories[2] = reader.histories[2][:1] + with pytest.raises(FullReproductionRefusal, match="every as-published stamp"): + reproduce_published_history(reader, sku="H100", target_date="2026-09-03") + + +@pytest.mark.parametrize("stamp,version", [ + ("2026-09-03T18:59:43.999Z", 1), + ("2026-09-03T18:59:44.000Z", 2), + ("2026-09-03T11:59:44-07:00", 2), +]) +def test_effective_time_selection_is_inclusive_and_timezone_aware(stamp, version): + reader = _two_version_reader() + target = copy.deepcopy(reader.histories[version][0]) + target["observed_at"] = stamp + reader.published = [target] + reader.histories[version] = [target] + result = reproduce_published_history(reader, sku="H100", target_date="2026-09-03") + assert result.checks[0].version == version + assert result.checks[0].verdict == VERDICT_MATCH + + +@pytest.mark.parametrize("pointer", [None, {"current_version": 2}]) +def test_as_published_full_requires_advertised_history(pointer): + reader = _two_version_reader() + reader.pointer = pointer + with pytest.raises(FullReproductionRefusal, match="does not advertise as-published history"): + reproduce_published_history(reader, sku="H100", target_date="2026-09-03") + + +def test_disclosure_bound_uses_history_and_longest_forward_horizon(): + from gpu_index.published.full import FULL_HISTORY_BOUND_DAYS + from gpu_index.published.verify import MIN_DISCLOSURE_WINDOW_DAYS, _history_bound_days + + assert FULL_HISTORY_BOUND_DAYS == MIN_DISCLOSURE_WINDOW_DAYS == 100 + assert _history_bound_days(history_days=90, forward_horizons_hours=[6, 49]) == 101 + assert _history_bound_days(history_days=120, forward_horizons_hours=[72]) == 131 + + +@pytest.mark.parametrize("pre_launch", [False, True]) +def test_default_command_reproduces_a_digest_verified_two_version_corpus(tmp_path, pre_launch): + from gpu_index.published.artifacts import payload_digest + + corpus = _two_version_reader() + target_date = "2026-09-03" + if pre_launch: + target_date = "2026-08-25" + row = copy.deepcopy(corpus.histories[1][0]) + row["observed_at"] = f"{target_date}T00:00:00.000Z" + corpus.published = [row] + corpus.histories[1] = [row] + template = json.loads((REPO_ROOT / "tests/fixtures/published/latest.json").read_text()) + root = tmp_path / "record" + + def write(key, data): + doc = copy.deepcopy(template) + doc["data"] = data + rows = data["observations"] + doc["meta"].update( + observation_count=len(rows), + from_observed_at=min(row["observed_at"] for row in rows), + to_observed_at=max(row["observed_at"] for row in rows), + ) + doc["artifact_sha256"] = payload_digest( + {key: doc[key] for key in ("data", "meta", "license")} + ) + path = root / key + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(doc)) + + pointer = corpus.pointer + pointer.update(sku="H100", **{key: pointer["succession"][-1][key] + for key in ("methodology_id", "effective_from")}) + write("latest.json", {"kind": "gpu_index_latest", "observations": corpus.published, + "versions": [pointer]}) + day_key = f"observations/{target_date.replace('-', '/')}.json" + write(f"H100/published/{day_key}", { + "kind": "gpu_index_observation_day", "date": target_date, + "observations": corpus.published, + }) + for version, rows in corpus.histories.items(): + if pre_launch and version == 2: + continue + write(f"H100/v{version}/{day_key}", { + "kind": "gpu_index_observation_day", "date": target_date, "observations": rows, + }) + write(f"H100/v{version}/series/90d.json", { + "kind": "gpu_index_series", "range": "90d", "observations": rows, + }) + env = {key: value for key, value in os.environ.items() if not key.startswith("GPU_INDEX_")} + env.update(PYTHON=sys.executable, GPU_INDEX_DATA_DIR=str(root)) + + def run(*flags): + return subprocess.run( + [str(REPO_ROOT / "reproduce"), *flags, "h100", target_date], + env=env, capture_output=True, text=True, timeout=30, + ) + + full = run() + assert full.returncode == 0, full.stderr + assert "raw-only full reproduction" in full.stdout + assert "NOTICE" not in full.stdout + count = 1 if pre_launch else 2 + assert f"{count} MATCH, 0 MISMATCH, 0 degraded" in full.stdout + assert "version 1 methodology_id h100_sxm_v1_calc_v8" in full.stdout + assert ("back-calculated" in full.stdout) == pre_launch + if not pre_launch: + assert "version 2 methodology_id h100_sxm_v1_calc_v10" in full.stdout + assert "derived 6.0" in full.stdout + receipts = run("--receipts") + assert receipts.returncode == 0, receipts.stderr + assert "raw-only full reproduction" not in receipts.stdout + assert f"{count} MATCH, 0 MISMATCH, 0 degraded" in receipts.stdout + # The fast check remains independent of version-history availability. + (root / f"H100/v1/{day_key}").unlink() + assert run().returncode == 2 + assert run("--receipts").stdout == receipts.stdout diff --git a/tests/unit/test_published_reader_cli.py b/tests/unit/test_published_reader_cli.py index 7aa5457..41b5443 100644 --- a/tests/unit/test_published_reader_cli.py +++ b/tests/unit/test_published_reader_cli.py @@ -668,15 +668,6 @@ def test_missing_advertised_history_does_not_fall_back(tmp_path, monkeypatch, cl assert "falling back" not in out.out -def test_full_requires_explicit_version_when_history_advertised( - tmp_path, monkeypatch, cli, capsys -): - root = _published_record(tmp_path) - monkeypatch.setattr(cli, "PublishedRecordReader", lambda: _local_reader(root)) - assert cli.main(["--sku", "H100", "--date", "2026-08-25", "--full"]) == 2 - assert "pass --version " in capsys.readouterr().err - - @pytest.mark.parametrize("version", ["0", "-1", "abc", "1.5"]) def test_cli_rejects_invalid_version(cli, version): with pytest.raises(SystemExit) as exc: diff --git a/tests/unit/test_reproduce_routing.py b/tests/unit/test_reproduce_routing.py index 3fd3fc4..1ff170f 100644 --- a/tests/unit/test_reproduce_routing.py +++ b/tests/unit/test_reproduce_routing.py @@ -151,8 +151,8 @@ def test_default_with_no_env_verifies_against_the_official_front( (line,) = _shim_lines(result) assert "verify_published_record.py" in line assert "--sku H100 --date 2026-08-24" in line - # Mixed-methodology as-published days use receipt verification. - assert "--full" not in line + # Default follows the effective methodology for each stamp. + assert "--full" in line assert _shim_env(result) == "https://data.getcomputable.com" @@ -455,7 +455,7 @@ def test_version_routes_in_both_public_modes(shim, data_dir, mode, version_first assert result.returncode == 0, result.stderr (line,) = _shim_lines(result) assert "--version 5" in line - assert ("--full" in line) == (mode == ["--full"]) + assert ("--full" in line) == (mode != ["--receipts"]) @pytest.mark.parametrize("mode", ["--collect", "--producer", "--lane", "--frozen"])