feat(carwash): review outbox, booth side — plate-blurred vehicle crop + the operator's choice, queued for a trusted remote reviewer
The operator's category choice is a hypothesis, not truth (user, 2026-09-06): each wash order with a vehicle read queues a package for a trusted reviewer over the private overlay (Netbird); the verdict becomes the phase-B training label and the per-operator error rate. wiki/concepts/vision-review-outbox.md. - Boxes: the vision service returns the vehicle bbox; snapshot.ts stores the vehicle and plate boxes on the read as FRACTIONS of the analysed frame (the stored snapshot is a downscaled copy); vehicleForIdentity() returns them. - carwash_review_outbox (migration 0031) + review-outbox.ts: crop = detector box + 8 % margin, ≤ 640 px, plate blurred in place from the plate box; payload carries a pseudonymous booth id and a keyed operator hash — no site name, no plate, no OSD, no bystanders; multipart POST with a per-booth bearer; 2xx → sent (image dropped); 400/404/413/415/422 → abandoned; anything else → backoff 1 min·2^n capped 6 h; voided orders and items older than 14 days abandoned unsent. Nothing queued while unconfigured. - Enqueue is fire-and-forget off the intake path in createOrder; the loop runs every CARWASH_REVIEW_INTERVAL_SEC (60) and stops on close. - GET /api/carwash/review/status (site:read) + a "Remote review" line in Setup → Car wash. - Env CARWASH_REVIEW_URL / _TOKEN / _BOOTH_ID (all three or off) documented in .env.example and forwarded by compose. - Tests: review-outbox.test.ts (crop + blur on a synthetic frame, config/pseudonyms, queue/drain/backoff/abandon, through the app). Wiki: new concept page, index, venue-modules As built, log. The collector is not built. Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
---
|
||||
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 <token>`,
|
||||
`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.
|
||||
@@ -196,6 +196,10 @@ onto the site's own categories ("car, sedan, hatchback → Vetura").
|
||||
dataset builds itself on park-2. Until then a Vetura/SUV list sees every car as Vetura and no
|
||||
downgrade fires; van/truck/bus/motorcycle do separate. Reports (discrepancies per operator per
|
||||
shift) wait for the first real reads.
|
||||
- **The operator's label is a hypothesis (user, 2026-09-06)** — the training label is a trusted
|
||||
remote reviewer's. Booth side built: [[vision-review-outbox]] (crop + blurred plate + choice,
|
||||
queued off the intake path, drained one-way over Netbird, no site identity leaves). The
|
||||
collector is the open half.
|
||||
|
||||
## Car Wash — the pilot module (settled 2026-09-05)
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ Counts: 4 sources · 19 entities · 47 concepts · 8 decision records.
|
||||
- [[append-only-event-chain]] — append-only + hash chain + signing = unforgeable log (signing is **software today**; hardware signer pending — see below).
|
||||
- [[hardware-signer-options]] — where the ledger signing key should live (TPM interim → USB-HSM target; ATECC608 upcoming, not on-site) so a host-owner can't forge the chain.
|
||||
- [[reconciliation]] — the real anti-fraud control; what remote sync actually is.
|
||||
- [[vision-review-outbox]] — the wash operator's category choice is a hypothesis: the booth queues a plate-blurred vehicle crop + the choice for a trusted remote reviewer over Netbird (one-way, offline-first, no site identity leaves); the verdict = training label + per-operator error/fraud rate.
|
||||
- [[disk-os-hardening]] — the *why* of host hardening: LUKS FDE + TPM-sealed auto-unlock (PCR 7) + Secure Boot + GRUB edit-lock + unprivileged operator + firmware/dbx lockdown; secondary control (reconciliation is the main event). Commands → [[appliance-provisioning]].
|
||||
- [[backup-recovery]] — admin-driven encrypted full-DB backup (local/SMB/SFTP) + DR; signing key escrowed & decoupled from TPM so the ledger survives total hardware loss; restore is admin-only; last-success/error status + schedule are now restart-durable (migration 0025, fixed a "shows Never despite valid backups" bug).
|
||||
|
||||
|
||||
+10
@@ -3100,3 +3100,13 @@ off), input size, detector floor; Dockerfile bakes yolox_s.onnx (best-effort cur
|
||||
path; compose forwards it (empty = off). Measured on four real dev entry frames: car at 0.83–0.88,
|
||||
~240–330 ms, empty lane → none. Tests: tests/test_vehicle.py (pure post-processing + composition +
|
||||
missing-model health). Updated [[opencv-anpr-service]], [[venue-modules]].
|
||||
|
||||
## [2026-09-06] ingest | Car Wash review outbox — booth side
|
||||
User: the operator cannot be fully trusted, so their category choice + the snapshot go to a
|
||||
trusted remote reviewer over Netbird, with the plate blurred and no site identity. Built the booth
|
||||
side: vision reads keep the vehicle and plate boxes as frame fractions (service bbox → client →
|
||||
device_events); `carwash_review_outbox` (0031); `review-outbox.ts` (crop with margin ≤ 640 px,
|
||||
plate blurred in place, pseudonymous booth id + keyed operator hash, multipart POST with a
|
||||
per-booth bearer, backoff, permanent rejections, void/expiry abandon, image dropped once sent);
|
||||
enqueue off the intake path in `createOrder`; `/api/carwash/review/status` + a Setup line; env +
|
||||
compose. New concept page [[vision-review-outbox]]; [[venue-modules]] As built; index.
|
||||
|
||||
Reference in New Issue
Block a user