Skip to content

feat(server): learn personalized ranking from served-page clicks (D2)#66

Merged
ErikChevalier merged 2 commits into
mainfrom
feat/click-personalization-served
Jun 2, 2026
Merged

feat(server): learn personalized ranking from served-page clicks (D2)#66
ErikChevalier merged 2 commits into
mainfrom
feat/click-personalization-served

Conversation

@ErikChevalier
Copy link
Copy Markdown
Contributor

What

PR D2 of the click-personalization feature: an owner-only /click redirector so searches run from the browser (through the local server) also train the model, matching the native app. Stacked on #65 (D1) — review/merge that first; this PR's base will retarget to main once D1 lands.

How it stays safe

  • /click?rid=..&pos=.. is loopback-only (a non-owner gets 404).
  • The server remembers each owner render (a fresh, unguessable rid → the displayed (url, host) order) in a small, bounded, in-memory map (never persisted). /click resolves the destination from that server state, never a caller-supplied URL, so it cannot be an open redirect and a network client cannot forge a rid.
  • A stale rid or bad pos falls back to home (303 /) and learns nothing.
  • Owner result links route through /click only when personalization is on; everyone else (and a disabled owner) gets the plain destination link. Network visitors get bare links and never train or see personalization.
  • The saver persists the update to the encrypted vault, and only when the feature is enabled.

Tests

ruff + ruff format + mypy clean; 552 pytest passed (5 new served-click tests: records the skip-above update + redirects to the right URL; network visitor gets 404 and no tracking links; forged/stale rid and bad pos fail safe; disabled feature renders plain links).

🤖 Generated with Claude Code

Base automatically changed from feat/click-personalization to main June 2, 2026 06:40
FlintWave and others added 2 commits June 1, 2026 23:44
…er-only)

Add an owner-only `/click` redirect so searches run from the browser
through the local server also train the personalization model, matching
the native app.

- server/app.py: a loopback-only `/click?rid=..&pos=..` route. The server
  remembers each owner render (a fresh unguessable id -> the displayed
  (url, host) order) in a small, bounded, in-memory map, and `/click`
  resolves the destination from that server state, never a caller-supplied
  URL. So it cannot be an open redirect, a network client gets 404 and
  cannot forge a rid, and a stale rid / bad pos falls back to home.
- The owner's result links route through `/click` only when
  personalization is on; everyone else (and a disabled owner) gets the
  plain destination link via a new render_results_page `link_builder`.
- server_controller wires personalization_saver so an owner click persists
  the update to the encrypted vault, and only when the feature is enabled.

Gate green: ruff + ruff format + mypy + 552 pytest (5 new served-click
tests: records + redirects, network 404 + no tracking links, forged/stale
rid and bad pos fail safe, disabled renders plain links).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ErikChevalier ErikChevalier force-pushed the feat/click-personalization-served branch from 49c55a0 to 99a8d92 Compare June 2, 2026 06:44
@ErikChevalier ErikChevalier merged commit 8ebc3c6 into main Jun 2, 2026
1 check passed
@ErikChevalier ErikChevalier deleted the feat/click-personalization-served branch June 2, 2026 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant