--- title: Vision review outbox — harvesting the operator's category choice for a trusted reviewer type: concept status: booth side built 2026-09-06; collector pending related: [venue-modules, opencv-anpr-service, threat-model, append-only-event-chain, network-isolation] --- # Vision review outbox **The idea (user, 2026-09-06).** The Car Wash desk asks the operator for the vehicle's category, and the entry camera now proposes one ([[venue-modules]] §Vehicle category from vision). The operator's choice is what we would love to train the body-type classifier on — but the operator **cannot be fully trusted** (mistake or intent; the [[threat-model]]). So the booth hands each decision to a **trusted party** who reviews the picture and the label remotely, and *that* verdict is the training label — and, per operator, the honest-mistake / fraud rate. The booths sit on a private zero-trust overlay (**Netbird**), so the hand-off can go to a very locked-down collector without exposing anything to the open internet. ## Rules (all enforced in `apps/server/src/modules/carwash/review-outbox.ts`) 1. **Offline-first, never on the intake path.** Creating a wash order *queues* a package (fire and forget — a failure is a log line); a background loop drains the queue when the overlay is up. The wash never waits on the network. 2. **One-way.** The booth POSTs; nothing ever comes back into the booth's decisions. The signed ledger ([[append-only-event-chain]]) stays the only record of what happened at the wash. Reviewer verdicts stay central and reach the owner as a report per site. 3. **Nothing that names the site leaves the booth.** - Only the vehicle **crop** (the detector's box + 8 % margin, ≤ 640 px) — no walls, no camera OSD (date / camera name burned into the frame), no bystanders. - The **plate is blurred inside the crop** on the booth, from the plate detector's own box. - The booth is a **pseudonymous id** set at deploy (`CARWASH_REVIEW_BOOTH_ID`); the operator is a **keyed hash** (`sha256(boothId:username)[:16]`). The mapping back to places and people is the reviewer's, held off the collector. The dataset export drops even those. - Boxes are stored as **fractions of the frame** on the vision read, so the crop is cut from the stored (downscaled) snapshot copy. 4. **The network is not the auth.** A per-booth bearer token on top of the overlay; the booth can do nothing at the collector but this one POST. Payloads are small (a crop ≈ 50–80 kB). 5. **Data minimisation.** Queued only when there is a vehicle box (no box = no sample); the image is dropped from the row once delivered; a voided order is abandoned unsent; anything older than 14 days is abandoned ("expired") rather than resurfacing a fortnight in a burst. ## The package `multipart/form-data`: `meta` (JSON) + `image` (JPEG). Meta = `{ v, booth, item, order, at, operator (hash), operatorCategory {id,name}, service, vision {class, confidence, categoryId}, downgraded, image {width, height, plateBlurred} }`. Headers: `Authorization: Bearer `, `X-Booth-Id`. ## Draining Every `CARWASH_REVIEW_INTERVAL_SEC` (60): due items oldest-first, 20 per pass. `2xx` → sent (image cleared). `400/404/413/415/422` → abandoned (the collector refused the package itself). Anything else (auth not yet fixed, 429, 5xx, timeout, no route) → retry with backoff `1 min · 2^attempts`, capped at 6 h. `GET /api/carwash/review/status` (site:read) and a line in Setup → Car wash show queued / delivered / abandoned + the last error. ## Config `CARWASH_REVIEW_URL`, `CARWASH_REVIEW_TOKEN`, `CARWASH_REVIEW_BOOTH_ID` — all three or the outbox is off and **nothing is queued** (an unbounded queue nobody drains is worse than none). Set per booth in the Komodo stack env; compose forwards them. ## Not built yet: the collector A deliberately small service on the overlay: one ingest endpoint (token per booth, size cap), one review screen (crop, the operator's pick, the camera's pick → the reviewer picks the truth), one export (crops + reviewer labels, nothing else) for phase B training. Keep it that small — it must not grow into a fleet console. The Netbird policy: booths may reach the collector's ingest port and nothing else on it.