¶ case file 4 of 7 · applied — filed 2026-02 · last verified 2026-08
status: shipped — web app live; the hosted classifier runs layer 1 only
↳ consigned at applied — ¶ 04 · 08:47 · waybill: sorted mail → manifest
Applied
A job tracker that reads the search out of the inbox. Connect Gmail and Applied fetches your mail, names each message, and turns the noise into a pipeline of real applications you can act on. It shipped twice: first as a native macOS app, now as a hosted web app — and the two share one backend package.
[ problem ] · § as found
The status of a job search scatters across Gmail, employer systems, and one-off messages. A spreadsheet can’t keep up: updates get missed, rows get retyped, and the record drifts from the truth. The first answer was a desktop app, which meant the record only existed on one machine.
constraints —
- Ask for the least Gmail access that can work — read-only, and metadata rather than message bodies.
- Classify noisy inbox messages into useful application states.
- Make row isolation the database’s job, not the query writer’s.
- Fit the whole classifier into a serverless slot, or be honest about which layers didn’t fit.
- Never let a routine sync delete an application the current scan happened to miss.
fig. 1 — the sorting line, settled: rules ⟶ e5 similarity ⟶ gated setfit. the clay gate is where the model defers to review.
a drawn plate, at rest — not a screenshot. the gates are the repository’s real three-layer path; the marks are illustrative and carry no counts. the measured numbers live in the receipts below.
[ architecture ] · § fig. 2, inked
Gmail hands over metadata, the classifier names it, Postgres files it under an identity the database itself checks, and a Next.js dashboard reads it back. One backend package serves this and the desktop app; the desktop branch is where the two heavier classifier layers still live.
Gmail
gmail.readonly — nothing wider
read-only
Metadata fetch
Subject, From, Date, snippet — no bodies
subject + snippet
Classifier
Rules on the hosted path; e5 + SetFit on the desktop one
layers 2–3 — desktop only
SwiftUI
The desktop app, still in the repo
verdicts, scoped by user
Postgres
RLS on, FORCE’d, per-transaction JWT claims
rows the role may see
FastAPI
One package serves the web app and the desktop one
pipeline state, over the caller’s jwt
Next.js
Pipeline board, review queue, stat tiles
no identity bound, no rows — the guc is unset and rls denies
[ decisions ] · § as filed
d1 — fetch gmail metadata, never message bodies · accepted
The subject line, the sender, and Gmail’s own snippet are enough to name an application email.1
1 tradeoff — A body-blind classifier gives up signal on ambiguous mail, and buys a privacy boundary that holds without being trusted.
d2 — run the hosted classifier on the rules layer alone · accepted
torch, sentence-transformers, and SetFit do not fit a serverless function slot — not the size limit, and not the cold start.2
2 tradeoff — The hosted verdict is weaker than the desktop one. The alternative was pretending otherwise, so the limit is written into the code, the tests, and the boundary rows below.
d3 — enforce row isolation in postgres, not in the handlers · accepted
Application-level `WHERE user_id = …` is one forgotten clause away from a leak.3
3 tradeoff — Every transaction pays a `set_config` round trip, and the app must run as a role that cannot bypass its own policies.
d4 — make routine sync additive; keep destruction behind a button · accepted
A bounded scan of a large inbox will miss applications it already found, and re-syncing wiped them.4
4 tradeoff — Stale rows survive until an explicit rebuild, which is the cheaper failure.
[ validation ] · § the receipts
validation
walks the receipts below, top to bottom, and marks each row this page can verify: a check where a pinned artifact resolves, a ring where the trail ends in an on-page capture, a dash where a claim is described only. walked once, the result settles here and stays.-
01claim: Connecting Gmail asks for gmail.readonly and nothing wider — and the consent step deliberately does not merge previously granted scopes.
-
02claim: The classifier is three layers — rules, e5 embeddings, SetFit — and SetFit stays off until its training gates are met.
[public]
-
03claim: The hosted fetch reads metadata only — Subject, From, Date, and Gmail’s own snippet. Full message bodies are never downloaded on the web path.
[public]
-
04claim: The backend suite runs at the pinned commit: 305 tests passed, 0 skipped, under the test/null-keyring environment. It read 278 passed and 10 skipped until 2026-08-03. The +27 is 10 CORS origin-policy tests, 7 benchmark-guard tests, and the 10 Postgres RLS tests that used to be the skips — those now provision their own postgres:16 rather than waiting on a database URL nobody supplied.
- artifact: applied @ 71b74f8 · backend/tests ↗
[public]
-
05claim: Rules and deterministic hybrid v3 gates both passed on 96 samples with macro-F1 0.9791.
[public]
-
06claim: The macOS Debug target built locally with xcodebuild against the JobTracker scheme, and the desktop app is still in the repository.
- artifact: applied @ 36a2f54 · apps/macos ↗
[local — verified on request]
-
07claim: The dashboard renders real applications: it reads the summary and application endpoints server-side and draws a pipeline board, a stage funnel, and a review queue from what comes back.
-
08claim: Row isolation is enforced by Postgres, not by the handlers: the app runs as a role that cannot bypass RLS, every transaction sets request.jwt.claims locally, and user_credentials is FORCE’d so even the table owner is held to the policies.
-
09claim: The hosted classifier runs the rules layer alone. On the serverless path it returns after layer 1 even when the rules were unsure — embeddings and SetFit are never imported there.
outcomes
-
10claim: Job updates land in a trackable pipeline instead of a spreadsheet — and now in a browser instead of on one Mac.
- artifact: getapplied.vercel.app ↗
[public]
-
11claim: All three classifier layers do run in a browser — as a public Hugging Face Space, on an int8 ONNX export of the same model. That is a separate deployment from the web app, not the verdict getapplied.vercel.app returns.
rows marked [local — verified on request] were run by me on personal or demo data · ci rows link the public run · repo pins are the exact commits verified 2026-08.
what i’m NOT claiming —
- I’m not claiming the hosted app runs the full three-layer classifier. On Vercel it runs the rules layer only — deliberately, because the model stack does not fit the function slot. Embeddings and SetFit stay on the desktop path and in the Hugging Face Space.
- This bullet used to say CI could not prove the RLS policies enforce, because the Postgres suite skipped unless a live database URL was supplied and no workflow supplied one. That stopped being true on 2026-07-31 and the disclaimer outlived it. backend-ci.yml now runs an rls-postgres job against a postgres:16 service, sets JOBTRACKER_TEST_PG_ADMIN_URL, and fails if that URL is missing rather than letting the module skip quietly — so all ten tests execute on every push. A stale disclaimer is the same broken receipt as a stale boast, and the harder one to catch, because nobody audits a claim that costs its author something.
- I’m not citing the repository’s README or docs/WEB_ARCHITECTURE.md as evidence for the web app. Both still describe apps/web as an unwired scaffold with a placeholder dashboard — they are behind the code, and this file cites the code.
- No production email-volume or user numbers are claimed. Source, migrations, and test runs are shown publicly; private email and application records are not shown.
[ corrections ] · § the register
erratum · 2026-07-26
Until today this file described a native macOS app whose “web beta is a scaffold”, and pinned every receipt at 3225eb4. That stopped being true: Applied ships as a hosted web app at getapplied.vercel.app. The caveat is retired because it is false now, not because it was wrong then — and the receipts are re-pinned to 36a2f54, the public head of the branch that carries the web app. The scaffold claim’s own successor is receipt 07.
erratum · 2026-07-26
The backend-suite row read 182 tests, audited at the old pin. Re-run at 36a2f54 on 2026-07-26 it is 271 passed and 10 skipped. Nothing was retracted — the tree grew, and the number moved with it. The 10 skips are named in the row rather than folded into the total.
note · 2026-07-26
The repository was renamed yadava5/jobtracker ⟶ yadava5/applied. GitHub still redirects the old links, so nothing this file ever published is broken; the pins name the current repository instead of relying on a redirect. The case-file route stays /projects/jobtracker/ for the same reason.
erratum · 2026-08-02
The backend-suite row read 271 passed and 10 skipped. Re-run on the provenance audit at the current head 0f2b63f: 278 passed, 10 skipped. Same movement as the 182 ⟶ 271 correction above and for the same reason — the tree grew seven tests, nothing was retracted, and the skips did not move. The row is re-pinned to the commit the new count was taken at; every other Applied receipt keeps 36a2f54, which is still where those source audits were done.
note · 2026-08-02
Provenance audit: the classifier claims were re-derived by running the code rather than reading about it. The rules gate passed live at 96 samples, macro-F1 0.9791, 2 misclassified — and the deterministic hybrid gate returned the identical numbers, which is the direct evidence for the attribution correction recorded earlier: the file named “hybrid” measures the regexes alone. The 201-rule figure was recomputed by importing jobtracker.classifier.rules and summing the pattern lists across all seven categories — 106 strong, 26 weak, 69 negative, plus 14 ATS domains. The eval set was counted from its own JSONL: 96 samples, 8 classes, 12 each. Every one of those figures matched what this file already said.
[ appendix ] · § artifacts, indexed
artifact index —
- README — the desktop-era record ↗[repo] · yadava5/applied @ 36a2f54
- Architecture docs ↗[repo] · yadava5/applied @ 36a2f54
- ML strategy and evaluation gates ↗[benchmark] · yadava5/applied @ 36a2f54
- Backend test suite ↗[repo] · yadava5/applied @ 36a2f54
- Web app source ↗[repo] · yadava5/applied @ 36a2f54