feat(collector): review collector skeleton — apps/collector, its own Komodo stack on the reviewer's host
CI / check (push) Failing after 40s
Build & push images / images (push) Failing after 32s
Build desktop / desktop (push) Successful in 5m24s

The far end of the Car Wash review outbox (wiki/concepts/vision-review-outbox.md): a small
Fastify + SQLite service in the monorepo (shares the payload contract and the class
vocabulary via @parking/shared), delivered to art-docker-station by its own stack so
nothing booth-side lands there and nothing of it on a booth.

- POST /ingest: bearer token per booth (constant-time), X-Booth-Id must match, multipart
  meta + JPEG (magic checked, 2 MB cap), meta validated against the contract, idempotent on
  the item id; crop stored at crops/<booth>/<item>.jpg on the volume + one items row.
- /review + /api/*: the reviewer's screen served by the process (Basic auth, one login):
  one pending crop at a time, operator's pick and camera's pick beside it, one button/key
  per vocabulary class + unusable + skip; stats per booth and per hashed operator
  (agree / disagree / unusable — disagree = the reviewer's class is outside the operator's
  category).
- GET /export/labels.csv: reviewed usable rows for training; formula-leading cells are
  neutralised (booth-supplied names). Crops stay on the volume for the trainer on the host.
- Booth payload now carries operatorCategory.classes so the comparison needs no site setup.
- Delivery: apps/collector/Dockerfile (monorepo context), docker-compose.collector.yml
  (bind to the overlay IP; commented `trainer` profile seam for the GPU), a third build
  step in build-images.yml, a `wash-collector` stack in komodo/resources.toml with one
  secret per booth referenced from both the collector's token list and the booth's own
  stack (park-2 lines templated, commented, DNS name for the URL).
- Tests: app.test.ts (ingest ok/dup/refusals, review + stats + export, config). Image
  built and smoke-tested locally (health, ingest, duplicate, auth, verdict, export).

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
2026-09-07 08:26:22 +02:00
parent ec44547122
commit b485e9870b
22 changed files with 1045 additions and 10 deletions
+43
View File
@@ -91,6 +91,12 @@ COOKIE_SECURE=0
# this set in Setup → Site). Unset = every registered module. See wiki/decisions/venue-modules.md.
# park-2 pilots the Car Wash module (2026-09-05).
MODULES_ENTITLED=parking,carwash
# Car Wash review outbox (wiki/concepts/vision-review-outbox.md): the collector's ingest URL
# on the Netbird overlay, this booth's pseudonymous id, and its token — the SAME secret the
# wash-collector stack lists under that id. Leave all three unset to keep the outbox off.
#CARWASH_REVIEW_URL=http://docker-station.nb.infra:8090/ingest
#CARWASH_REVIEW_BOOTH_ID=booth-2
#CARWASH_REVIEW_TOKEN=[[wash_review_token_booth_2]]
VISION_ENABLED=1
# Desktop app WS handshake: Origin is tauri://localhost (set explicitly by
# platform-ws.ts, since the native WS plugin has no page context to auto-attach
@@ -100,3 +106,40 @@ JWT_SECRET=[[park_2_jwt_secret]]
EVENT_SIGNING_KEY=[[park_2_event_signing_key]]
BACKUP_KEY=[[park_2_backup_key]]
"""
##############################################################################
# Stack — the Car Wash REVIEW COLLECTOR on the reviewer's host (art-docker-station),
# NOT a booth. Same repo/branch/TAG promotion as the booths, but its file_paths name
# ONLY docker-compose.collector.yml, so nothing booth-side lands here and nothing here
# lands on a booth. Booths reach it over the Netbird overlay only (bind to the overlay
# address). Its own secrets. See wiki/concepts/vision-review-outbox.md.
##############################################################################
[[stack]]
name = "wash-collector"
[stack.config]
server = "art-docker-station"
git_provider = "git.infra.msai.al"
git_account = "komodo"
repo = "mca/parking_solution"
branch = "stage"
file_paths = [
"docker-compose.collector.yml"
]
registry_provider = "git.infra.msai.al"
registry_account = "komodo"
environment = """
REGISTRY=git.infra.msai.al/mca/parking_solution
# Pinned like the booths: bump to the stage-<sha> that carries the collector.
TAG=stage-REPLACE
# The host's NETBIRD address (an IP: Docker port bindings take no hostname) — the ingest port
# is published on the overlay only. Booths reach it by its Netbird DNS name.
COLLECTOR_BIND=100.75.184.156
# "<boothId>:<token>" pairs, one per booth. ONE secret per booth, referenced here AND in
# that booth's own stack as its CARWASH_REVIEW_TOKEN — one value, two consumers, nothing
# to keep in sync, and rotating a booth touches one secret. The booth id is the booth's
# pseudonymous CARWASH_REVIEW_BOOTH_ID, never a site name. Add a pair per booth.
COLLECTOR_BOOTH_TOKENS=booth-2:[[wash_review_token_booth_2]]
COLLECTOR_REVIEWER_USER=reviewer
COLLECTOR_REVIEWER_PASS=[[wash_collector_reviewer_pass]]
"""