Commit Graph

3 Commits

Author SHA1 Message Date
julian 3429642edb permits: admin CRUD (route + UI)
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.
2026-06-15 19:53:03 +02:00
julian c24d99b0f4 server: permit entry/exit branch + read dispatcher
A credential read now routes by what the credential IS: matches a permit
(card/QR credential or a bound plate) -> permit flow; else -> transient exit
flow. Lane resolved once (readerLaneWithAccess); ExitFlow.onRead -> handleAt so
the dispatcher owns lane resolution.

Permit direction is inferred from session state for that car (the read value is
the per-car session key): no open session -> ENTRY (enforce maxConcurrent, sign
vehicle_entry, open); open -> EXIT (sign vehicle_exit, open, close). Fleet
permit = one session per car; anti-passback falls out naturally.

maxConcurrent enforced as a fold over the signed ledger (null = unbound).
Validity window + status + plate-OR-card identity as designed. No ticket/fee;
every use is a signed event carrying permitId. Refusals (revoked / out-of-window
/ at-capacity) are signed anomalies, barrier stays closed.

Verified against stubs: card entry -> inferred exit; fleet cap 2 (F3 rejected
at 2/2, then admitted after F1 exits); plate-bound opens; revoked rejects;
unknown credential falls through to exit reject; verifyChain ok.
2026-06-15 19:47:01 +02:00
julian 8a8e74561d wiki: design the business layer (session, tariff, permit, vision, shift, ops)
Pivot from the hardware/integrity layer to the parking operation. All
wiki-only; no code yet. Core principle throughout: business entities are
projections over the signed append-only event log, never mutable tables.

New concepts: parking-session, tariff (composable/versioned, FX-ready),
shift (manned-only Z-report), capacity-occupancy, validation-discounts,
reporting-analytics, clock-integrity, ticket-encoding, anti-passback.
New entities: permit, opencv-anpr-service, blocklist.
Decisions: session-model, vision-service (host-side ANPR + vehicle
verification; scoped AGPL exception for the isolated service).

Updates: append-only-event-chain (new event types + vision witness),
local-jwt-auth (drop 8h expiry -> until logout; code change pending),
lpr-camera (host-side recognition supersedes edge-AI), standing-decisions
(AGPL exception), open-questions (+FX, +pay-station money corners, backup).

Deferred + flagged: intercom/help-call, receipts/refunds/change, FX engine,
lane topology (#1).
2026-06-15 17:41:38 +02:00