{"record":{"id":"0b42aa05df761e63","repo":"langflow-ai/langflow","slug":"since-must-be-strictly-less-than-until","errorCode":null,"errorMessage":"`since` must be strictly less than `until`","messagePattern":"`since` must be strictly less than `until`","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"src/backend/base/langflow/api/v1/authz_audit.py","lineNumber":86,"sourceCode":"        Query(description=\"Filter by action string, e.g. ``flow:read`` or ``share:create``.\"),\n    ] = None,\n    result: Annotated[\n        str | None,\n        Query(description=\"Filter by decision result (``allow`` / ``deny`` / ``owner_override``).\"),\n    ] = None,\n    since: Annotated[datetime | None, Query(description=\"Inclusive lower bound on ``timestamp``.\")] = None,\n    until: Annotated[datetime | None, Query(description=\"Exclusive upper bound on ``timestamp``.\")] = None,\n    page: Annotated[int, Query(ge=1)] = 1,\n    size: Annotated[int, Query(ge=1, le=_MAX_PAGE_SIZE)] = 50,\n) -> AuthzAuditPage:\n    \"\"\"Return a paginated slice of the audit log filtered by the given query params.\n\n    Superuser only. The composite indexes on ``(user_id, timestamp)`` and\n    ``(resource_type, resource_id)`` keep both \"show me events for user X\"\n    and \"show me events on resource Y\" fast at scale.\n    \"\"\"\n    if since is not None and until is not None and since >= until:\n        raise HTTPException(status_code=400, detail=\"`since` must be strictly less than `until`\")\n\n    base = select(AuthzAuditLog)\n    if user_id is not None:\n        base = base.where(AuthzAuditLog.user_id == user_id)\n    if resource_type is not None:\n        base = base.where(AuthzAuditLog.resource_type == resource_type)\n    if resource_id is not None:\n        base = base.where(AuthzAuditLog.resource_id == resource_id)\n    if action is not None:\n        base = base.where(AuthzAuditLog.action == action)\n    if result is not None:\n        base = base.where(AuthzAuditLog.result == result)\n    if since is not None:\n        base = base.where(AuthzAuditLog.timestamp >= since)\n    if until is not None:\n        base = base.where(AuthzAuditLog.timestamp < until)\n\n    # Two queries: one COUNT(*) for pagination metadata, one for the page","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/authz_audit.py#L68-L104","documentation":"Validation error from GET /api/v1/authz/audit: when both since and until query params are supplied, since must be strictly less than until. since is an inclusive lower bound and until an exclusive upper bound on the audit timestamp, so an equal or inverted range is rejected with HTTP 400 before the query runs.","triggerScenarios":"GET /api/v1/authz/audit?since=2026-01-01T00:00:00Z&until=2026-01-01T00:00:00Z (equal bounds), or any range where since > until, e.g. copy-pasting timestamps with the times swapped or mixing timezone-offsets so the comparison inverts.","commonSituations":"UI date-range pickers that default start and end to the same instant, clients building ranges from two identical 'now' timestamps, or timezone confusion where an ISO string with +02:00 looks later than a UTC string but parses earlier.","solutions":["Ensure the client sends since < until strictly; when the picker returns equal timestamps, shift until forward by at least one second","Send both timestamps in UTC with explicit offsets (e.g. Z) to avoid timezone inversion","Validate the range client-side before issuing the request","If you want a single instant, use only since and let until be null"],"exampleFix":"// before\nconst q = `?since=${start.toISOString()}&until=${end.toISOString()}`;\n\n// after\nif (since >= until) throw new RangeError('since must be < until');\nconst q = `?since=${start.toISOString()}&until=${end.toISOString()}`;","handlingStrategy":"validation","validationCode":"function validRange(since: Date, until: Date) {\n  return since.getTime() < until.getTime();\n}\nif (!validRange(since, until)) throw new RangeError('since must be < until');","typeGuard":"const isSafeRange = (s?: string, u?: string): boolean =>\n  s == null || u == null || new Date(s).getTime() < new Date(u).getTime();","tryCatchPattern":null,"preventionTips":["Default date pickers to start-of-day / end-of-day rather than the same instant","Always send UTC ISO strings with explicit offsets","Client-side assert since < until before building the query string"],"tags":["authz","audit","validation","http-400","time-range"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}