Both specs said to match "on company + role" with "casefold + strip on
both fields", but a seen_jobs.json entry has title and no role key (the
job-scraper schema), so read literally the lookup never matched: /upskill
then weighted the same job twice, once as a tracker row and once as a
ranked entry - the double-count its dedupe step exists to prevent - and
/notion-sync split one job into two rows with different Keys.
tools/rank_state.py already does the cross-name join (tracker role
against entry title); both specs now say so.
Two derived tests read the entry schema and require the join wording to
name title for the entry and role for the tracker; they fail on master.
Adds /notion-sync per the conditions agreed in discussion #166: tool-agnostic sync contract with Notion as the in-tree reference binding, silently optional (covers unconfigured, headless, and unauthenticated states), read-only toward the repo with the gitignored sync-state file as its only local write, write-once page bodies, documents sync as filenames only. Complements /html-report: deep local dashboard vs glanceable anywhere-view.