Trainer: `parking-trainer serve` — a stdlib HTTP job API on the compose network (never
published): /health, /readiness, /versions, /versions/<v>/report, /jobs. One job at a
time; each job runs the CLI as a subprocess with its output captured, state + log
persisted under /out/jobs/ so a restart keeps history. `publish` takes its URL from
TRAINER_PUBLISH_URL. Dockerfile: CMD serve, EXPOSE 8091, healthcheck.
Collector: COLLECTOR_TRAINER_URL + /api/training/{status,jobs,jobs/:id,versions/:v/report}
— a reviewer-gated proxy that forwards a fixed set of paths and whitelisted knobs and
passes the trainer's status codes through (409 while a job runs; 503 unconfigured, 502
unreachable). /review gains the Training section: labels per class vs the minimum with
Train disabled until two classes clear it, mode / backbone / floor, the running job's
live log, the versions with Report / Evaluate / Publish (publish confirms), and the
reminder that pinning stays a git commit. Fixed on the way: an apostrophe in the page's
inline script broke the whole page — a test now parses the script.
Compose: `trainer` is a service (restart: unless-stopped, read-only data volume, its own
trainer-out volume), the `train` profile and TRAINER_OUT are gone; the Docker-socket
route was rejected (root on the host for a service booths upload to). Verified with both
images running together: a Train started through the proxy finished, version and report
came back, the page rendered.
Wiki: bodytype-classifier-training (loop, running it, operating notes superseded),
vision-review-outbox, fleet-deployment-komodo, log.
Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
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