666ghj/MiroFish · error · StarHistoryError

reconstruction date is not canonical

Error message

reconstruction date is not canonical

What it means

Raised by validate_state() when a daily date string parses via date.fromisoformat but does not round-trip to its canonical isoformat() representation. This rejects non-padded or oddly formatted dates like '2024-1-1' or '2024-01-1' that Python may still parse, enforcing one canonical representation.

Source

Thrown at scripts/star_history.py:410

        raise StarHistoryError("reconstruction.daily must be a list")
    if reconstruction_method == "aggregate_snapshot_only" and daily:
        raise StarHistoryError("aggregate-only history cannot contain reconstructed dates")

    previous_day: date | None = None
    previous_stars = 0
    for index, raw_point in enumerate(daily):
        if not isinstance(raw_point, dict):
            raise StarHistoryError("reconstruction point must be an object")
        _expect_keys(raw_point, {"date", "stars"}, "reconstruction point")
        raw_date = raw_point["date"]
        if not isinstance(raw_date, str):
            raise StarHistoryError("reconstruction date must be a string")
        try:
            point_day = date.fromisoformat(raw_date)
        except ValueError as exc:
            raise StarHistoryError("reconstruction date is invalid") from exc
        if point_day.isoformat() != raw_date:
            raise StarHistoryError("reconstruction date is not canonical")
        stars = _strict_non_negative_int(raw_point["stars"], "reconstruction stars")
        if index == 0 and stars <= 0:
            raise StarHistoryError("first reconstruction point must have stars")
        if previous_day is not None and point_day <= previous_day:
            raise StarHistoryError("reconstruction dates must be strictly increasing")
        if index > 0 and stars <= previous_stars:
            raise StarHistoryError("reconstruction stars must be strictly increasing")
        if point_day >= generated_at.date():
            raise StarHistoryError("reconstruction must contain only completed UTC dates")
        previous_day = point_day
        previous_stars = stars

    snapshots = state["snapshots"]
    if not isinstance(snapshots, list):
        raise StarHistoryError("snapshots must be a list")
    previous_snapshot: datetime | None = None
    first_snapshot: datetime | None = None
    for raw_snapshot in snapshots:

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Always produce dates with d.isoformat() or '%Y-%m-%d' formatting
  2. Fix generator code that formats dates manually
  3. Validate round-trip equality in tests: date.fromisoformat(s).isoformat() == s

Example fix

# before
f"{point.year}-{point.month}-{point.day}"
# after
point.isoformat()
Defensive patterns

Strategy: validation

Validate before calling

for p in daily:
    d = date.fromisoformat(p["date"])
    if d.isoformat() != p["date"]:
        p["date"] = d.isoformat()  # canonicalize

Type guard

def are_canonical_dates(daily: list) -> bool:
    return all(
        date.fromisoformat(p["date"]).isoformat() == p["date"] for p in daily
    )

Try / catch

try:
    validate_state(state)
except StarHistoryError as exc:
    if "reconstruction date is not canonical" in str(exc):
        for p in daily:
            p["date"] = date.fromisoformat(p["date"]).isoformat()
        validate_state(state)  # re-validate after canonicalization

Prevention

When it happens

Trigger: validate_state(state) where date is '2024-1-1', '2024-01-1', or any form where date.fromisoformat(raw).isoformat() != raw.

Common situations: Date formatting code using f"{y}-{m}-{d}" without zero padding; locales/serializers that strip leading zeros; manual date entry.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/d73357fa4110450d. Report an issue: GitHub.