{"record":{"id":"d73357fa4110450d","repo":"666ghj/MiroFish","slug":"reconstruction-date-is-not-canonical","errorCode":null,"errorMessage":"reconstruction date is not canonical","messagePattern":"reconstruction date is not canonical","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":410,"sourceCode":"        raise StarHistoryError(\"reconstruction.daily must be a list\")\n    if reconstruction_method == \"aggregate_snapshot_only\" and daily:\n        raise StarHistoryError(\"aggregate-only history cannot contain reconstructed dates\")\n\n    previous_day: date | None = None\n    previous_stars = 0\n    for index, raw_point in enumerate(daily):\n        if not isinstance(raw_point, dict):\n            raise StarHistoryError(\"reconstruction point must be an object\")\n        _expect_keys(raw_point, {\"date\", \"stars\"}, \"reconstruction point\")\n        raw_date = raw_point[\"date\"]\n        if not isinstance(raw_date, str):\n            raise StarHistoryError(\"reconstruction date must be a string\")\n        try:\n            point_day = date.fromisoformat(raw_date)\n        except ValueError as exc:\n            raise StarHistoryError(\"reconstruction date is invalid\") from exc\n        if point_day.isoformat() != raw_date:\n            raise StarHistoryError(\"reconstruction date is not canonical\")\n        stars = _strict_non_negative_int(raw_point[\"stars\"], \"reconstruction stars\")\n        if index == 0 and stars <= 0:\n            raise StarHistoryError(\"first reconstruction point must have stars\")\n        if previous_day is not None and point_day <= previous_day:\n            raise StarHistoryError(\"reconstruction dates must be strictly increasing\")\n        if index > 0 and stars <= previous_stars:\n            raise StarHistoryError(\"reconstruction stars must be strictly increasing\")\n        if point_day >= generated_at.date():\n            raise StarHistoryError(\"reconstruction must contain only completed UTC dates\")\n        previous_day = point_day\n        previous_stars = stars\n\n    snapshots = state[\"snapshots\"]\n    if not isinstance(snapshots, list):\n        raise StarHistoryError(\"snapshots must be a list\")\n    previous_snapshot: datetime | None = None\n    first_snapshot: datetime | None = None\n    for raw_snapshot in snapshots:","sourceCodeStart":392,"sourceCodeEnd":428,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L392-L428","documentation":"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.","triggerScenarios":"validate_state(state) where date is '2024-1-1', '2024-01-1', or any form where date.fromisoformat(raw).isoformat() != raw.","commonSituations":"Date formatting code using f\"{y}-{m}-{d}\" without zero padding; locales/serializers that strip leading zeros; manual date entry.","solutions":["Always produce dates with d.isoformat() or '%Y-%m-%d' formatting","Fix generator code that formats dates manually","Validate round-trip equality in tests: date.fromisoformat(s).isoformat() == s"],"exampleFix":"# before\nf\"{point.year}-{point.month}-{point.day}\"\n# after\npoint.isoformat()","handlingStrategy":"validation","validationCode":"for p in daily:\n    d = date.fromisoformat(p[\"date\"])\n    if d.isoformat() != p[\"date\"]:\n        p[\"date\"] = d.isoformat()  # canonicalize","typeGuard":"def are_canonical_dates(daily: list) -> bool:\n    return all(\n        date.fromisoformat(p[\"date\"]).isoformat() == p[\"date\"] for p in daily\n    )","tryCatchPattern":"try:\n    validate_state(state)\nexcept StarHistoryError as exc:\n    if \"reconstruction date is not canonical\" in str(exc):\n        for p in daily:\n            p[\"date\"] = date.fromisoformat(p[\"date\"]).isoformat()\n        validate_state(state)  # re-validate after canonicalization\n","preventionTips":["Format dates with d.isoformat() or '%Y-%m-%d', never manual f-strings","Canonicalize external date strings before storing them","Assert round-trip equality in serialization tests"],"tags":["validation","date","canonical-form"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}