3429642edb
A permit is an aggregate (row + credentials + bound plates); create/update treat it as one unit (child sets replaced on update). GET /api/permits (any signed-in role, for lookup); POST/PUT/DELETE + POST /:id/revoke (admin only). Validation: maxConcurrent positive-int-or-null (unbound); a permit must have at least one credential OR one bound plate. Revoke is the soft common case (keeps history, barred at the barrier); DELETE hard-removes — past ledger events that reference it are untouched (append-only audit trail, independent of this row). Web PermitManager in the admin shell: list + add/edit (holder, car-bound toggle, validity, credentials, plates), revoke, delete. Makes permits usable without hand-seeding (companion to the tariff composer). Verified via inject: validation (empty / maxConcurrent=0 -> 400), create -> 201, operator can LIST but not write (403), update replaces child rows, revoke -> revoked, delete -> 204 then 404 with children cleaned.