fix(server): reject inverted period_start/period_end in CameraRecord

A misbehaving or clock-broken device could submit period_end <=
period_start, polluting the camera_records table with zero-length or
inverted windows that corrupt downstream hourly analytics. Add a
Pydantic model_validator so the request is rejected at the API
boundary instead of silently persisting bad ranges.

Found via adversarial review (run 2026-05-01-191359, both reviewers).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 15:44:57 -07:00
parent 8342904488
commit 641ab29277
2 changed files with 19 additions and 1 deletions

View File

@@ -11,7 +11,7 @@ import sqlite3
from typing import List from typing import List
from fastapi import Depends from fastapi import Depends
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, model_validator
class CameraRecord(BaseModel): class CameraRecord(BaseModel):
@@ -20,6 +20,12 @@ class CameraRecord(BaseModel):
entries: int = Field(ge=0) entries: int = Field(ge=0)
exits: int = Field(ge=0) exits: int = Field(ge=0)
@model_validator(mode="after")
def _period_order(self):
if self.period_end <= self.period_start:
raise ValueError("period_end must be strictly greater than period_start")
return self
class CameraEventsRequest(BaseModel): class CameraEventsRequest(BaseModel):
location_id: str location_id: str

View File

@@ -98,3 +98,15 @@ def test_negative_counts_rejected():
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
CameraRecord(period_start=1712000000, period_end=1712003600, CameraRecord(period_start=1712000000, period_end=1712003600,
entries=-1, exits=0) entries=-1, exits=0)
def test_inverted_period_rejected():
"""Pydantic should reject period_end <= period_start."""
from pydantic import ValidationError
from server.camera_endpoint import CameraRecord
with pytest.raises(ValidationError):
CameraRecord(period_start=1712003600, period_end=1712003600,
entries=0, exits=0)
with pytest.raises(ValidationError):
CameraRecord(period_start=1712003600, period_end=1712000000,
entries=0, exits=0)