Skip to content

gmail: a search reads newer: as after: and older: as before: - #745

Merged
nuriyoo merged 4 commits into
brekkylab:mainfrom
WynandVStaden:gmail-newer-older-are-after-before
Oct 16, 2026
Merged

nuriyoo merged 4 commits into
brekkylab:mainfrom
WynandVStaden:gmail-newer-older-are-after-before

Conversation

@WynandVStaden

@WynandVStaden WynandVStaden commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Closes #553.

What changed

A Gmail search now reads newer: as after: and older: as before:. _parse_gmail_q in backlot/routers/google.py takes both as operators and files each under the operator it is another name for, through _GMAIL_KEY_ALIASES, so the SQL push-down and _gmail_op_match treat them exactly as they treat after: and before:. Before this, neither was in _GMAIL_KEYS, so each stayed in the free text, matched nothing full-text, and a query holding one answered an empty page.

Why this is what the real API does

The measurement is the one in #553, against gmail.googleapis.com on 2026-10-09 by the issue's author, on a mailbox of 565 messages: newer:2026/10/01 answered the same 55 messages as after:2026/10/01, and older:2026/10/01 filled a 500 page as before:2026/10/01 did. The Gmail search reference lists the four operators as one entry. I have no Gmail token and did not call the real API. The comment beside _GMAIL_KEY_ALIASES records a maintainer's measurement on a Workspace mailbox on 2026-10-15, which compared the ids messages.list and threads.list answered.

Verification

  • Tests — pytest on a .[dev] install, Ubuntu 24.04 (WSL), Python 3.12.3: 6070 passed, 37 skipped. test_gmail_newer_older_are_after_before in tests/test_search.py fails with backlot/ from main and passes with this change. The issue's reproduction prints [] for newer:2026/03/02 and older:2026/03/02 on main; here it prints the same subjects as after:2026/03/02 and before:2026/03/02.

  • Optional surfaces that ran rather than skipped — none. This was a .[dev] install only, so tests/test_sdk.py, tests/test_s3.py, tests/test_mcp.py, tests/test_llamaindex.py and the reader, Google and mirage tests in tests/test_integrations.py sat out. The change touches backlot/routers/google.py and tests/test_search.py only.

  • Lint — ruff check . && ruff format --check .: clean. python scripts/prose_check.py upstream/main: no findings.

  • A test fails without this change

  • A new endpoint serving corpus content is ACL-scoped, proved for both an admin and a scoped token

  • Comments state what was measured, not the history of the fix

The second box is not ticked because no endpoint is added.

@nuriyoo nuriyoo added the fidelity The vendor API defines the right answer; closing this needs a measurement against it label Oct 15, 2026

@nuriyoo nuriyoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reads newer: and older: the way real does. I measured it against gmail.googleapis.com on 2026-10-15 with a Workspace user's gmail.readonly token, over a mailbox of 595 messages, comparing the ids each query returned rather than counts: messages.list answered newer:X with the ids of after:X, and older:X with those of before:X, in all 42 queries tried that give the operator a value. Those included 12 values (2026/10/01, 2026-10-01, 2026/10/1, 10/01/2026, seconds, and values real reads as no date such as abc and 2026/13/01), upper case, -newer:, the two together, each repeated, newer: beside after: in either order, and a space after the colon. threads.list agreed in the four I sent there. Your branch folds each alias before anything reads it, so it answers all of those as it answers the twin; a replay of the cases the parser handles differently (case, repeats in both orders, the empty value) over a five-message corpus agreed on messages and threads. Two small things, both suggested inline.

1. An empty newer: or older: is not an empty after: or before:. On real an empty one is a search term of its own name: newer: answers the messages newer does, and after: those of after (37 here), so the two differ. Your branch already answers it that way, since _GMAIL_OP needs a character after the colon, but nothing pins it: with the value made optional (\S* for \S+) both fold into an after: or before: with an empty value, which _gmail_date skips, and the test still passes. The two rows in the test suggestion fail on that change.

2. The comment's measurement. It cites the issue's counts; the suggestion records the id comparison above and the empty value, and the test docstring points at it. If you take it, the last sentence of the body's Why section can say the comment records the 2026-10-15 comparison.

Measuring this also showed how after: and before: read their own values (a month-first date, values real matches nothing for, a space after the colon, -after:), where Backlot differs; that is not this pull request's, and is filed as #751.

At e3140cc (Python 3.12, .[dev]):
  pytest tests/test_search.py -k gmail                              6 passed
  the same with backlot/routers/google.py from main                 test_gmail_newer_older_are_after_before fails
  9 mutations (each new key, each alias dropped or swapped, both    7 caught; `\S+` -> `\S*` and `.strip()` -> `.strip(" \t")` survive
    to after, the alias lookup, the value made optional, strip)
With the two suggestions:
  pytest (the whole suite)                                          6216 passed, 37 skipped
  the same 9 mutations                                              8 caught; `.strip()` survives (a line this does not change)
  ruff check, ruff format --check                                   clean

Comment thread backlot/routers/google.py Outdated
Comment thread tests/test_search.py Outdated
…ty values are pinned

On a Workspace mailbox on 2026-10-15 messages.list and threads.list answered newer:X with the ids after:X answers and older:X with those of before:X; an empty newer: or older: is a search term.

@nuriyoo nuriyoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. I applied the review's suggestion as written, as a maintainer commit, 64b7465: the comment on _GMAIL_KEY_ALIASES records the 2026-10-15 measurement, which compared the ids messages.list and threads.list answered, and the test pins an empty newer: and older: as search terms, as _GMAIL_OP already reads them. How real reads the date values themselves is filed separately. The description's line on the comment now names that measurement.

At 19f6889 (main at 52d03e0 merged in; the tree matches a local merge of the two):
  pytest (the whole suite, Python 3.13, .[all,dev])    9339 passed, 2 skipped
  ruff check, ruff format --check                      clean

@nuriyoo
nuriyoo merged commit 7a551d0 into brekkylab:main Oct 16, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fidelity The vendor API defines the right answer; closing this needs a measurement against it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gmail: newer: and older: are matched as free text, where real reads them as after: and before:

2 participants