{"record":{"id":"8973e32072bc48e9","repo":"666ghj/MiroFish","slug":"snapshot-timestamps-must-be-strictly-increasing","errorCode":null,"errorMessage":"snapshot timestamps must be strictly increasing","messagePattern":"snapshot timestamps must be strictly increasing","errorType":"exception","errorClass":"StarHistoryError","httpStatus":null,"severity":"error","filePath":"scripts/star_history.py","lineNumber":435,"sourceCode":"            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:\n        if not isinstance(raw_snapshot, dict):\n            raise StarHistoryError(\"snapshot must be an object\")\n        _expect_keys(raw_snapshot, {\"at\", \"stars\"}, \"snapshot\")\n        snapshot_at = _parse_state_timestamp(raw_snapshot[\"at\"], \"snapshot.at\")\n        _strict_non_negative_int(raw_snapshot[\"stars\"], \"snapshot stars\")\n        if previous_snapshot is not None and snapshot_at <= previous_snapshot:\n            raise StarHistoryError(\"snapshot timestamps must be strictly increasing\")\n        if first_snapshot is None:\n            first_snapshot = snapshot_at\n        previous_snapshot = snapshot_at\n\n    if first_snapshot is not None:\n        if first_snapshot < generated_at:\n            raise StarHistoryError(\"first snapshot cannot predate reconstruction\")\n        if previous_day is not None and previous_day >= first_snapshot.date():\n            raise StarHistoryError(\"reconstruction dates must predate snapshots\")\n\n\ndef canonical_state_bytes(state: Mapping[str, Any]) -> bytes:\n    validate_state(state)\n    return (\n        json.dumps(state, ensure_ascii=False, indent=2, sort_keys=True) + \"\\n\"\n    ).encode(\"utf-8\")\n\n","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/666ghj/MiroFish/blob/b5b53acc57189a4a42e44a23e149dc655c98fe82/scripts/star_history.py#L417-L453","documentation":"Raised by validate_state() when snapshot 'at' timestamps are not strictly increasing across the snapshots array (a duplicate or out-of-order timestamp fails). Snapshots are appended once per INTERVAL_DAYS run, so order reflects chronological append order.","triggerScenarios":"validate_state(state) where snapshots[i]['at'] <= snapshots[i-1]['at'] — e.g. two snapshots with identical timestamps or a list sorted by stars instead of time.","commonSituations":"Merging state files by naive concatenation without re-sorting; a state rewrite that lost ordering; clock skew causing an equal or earlier timestamp on a later run.","solutions":["Sort snapshots by 'at' ascending and remove duplicate timestamps before writing state","When merging histories, dedupe on 'at' keeping the latest write","Re-run the snapshot interval to append a fresh, later timestamp"],"exampleFix":"# before\nsnapshots = snapshots + merged_snapshots  # unsorted concat\n# after\nsnapshots = sorted({s[\"at\"]: s for s in snapshots + merged_snapshots}.values(), key=lambda s: s[\"at\"])","handlingStrategy":"validation","validationCode":"snaps = sorted({s[\"at\"]: s for s in snaps}.values(), key=lambda s: s[\"at\"])","typeGuard":"def snapshot_timestamps_increasing(snaps: list) -> bool:\n    return all(snaps[i][\"at\"] < snaps[i + 1][\"at\"] for i in range(len(snaps) - 1))","tryCatchPattern":"try:\n    validate_state(state)\nexcept StarHistoryError as exc:\n    if \"snapshot timestamps must be strictly increasing\" in str(exc):\n        # sort by 'at' and deduplicate timestamps, then re-validate\n        ...","preventionTips":["Append snapshots chronologically; never reorder the array","Deduplicate on 'at' when merging state files","Ensure the run clock is UTC to avoid skewed timestamps"],"tags":["validation","sorting","state-file"],"backgroundTag":null,"analyzedSha":"b5b53acc57189a4a42e44a23e149dc655c98fe82","analyzedAt":"2026-08-14T22:29:33.146Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}