server+web: capacity / FULL gate (occupancy fold + transient refuse)
Occupancy is a fold over the signed ledger (entries minus exits per identity);
getOccupancy returns {count, capacity, free, full}. Capacity is a single-row
site_config table (admin-set; null = uncapped; migration 0001, additive).
FULL gate lives in the transient entry flow: when full, refuse (no ticket, no
vehicle_entry, no open) and sign an anomaly. Permit entry is NOT gated --
subscribers are admitted past transient-full (their own maxConcurrent still
applies), so occupancy can read over capacity by design (reserve-for-permits).
Routes: GET /api/occupancy + GET /api/site-config (any role), PUT
/api/site-config (admin; non-negative int or null). Web SiteSettings: live
occupancy + FULL badge (everyone), capacity editor (admin).
Verified: fill to cap -> 3rd transient refused; permit admitted past full; exit
frees a slot; RBAC (operator can't set, -5 -> 400); verifyChain ok. Physical
FULL-sign relay output deferred.
This commit is contained in:
@@ -41,8 +41,27 @@ over, stacked beyond the marked count) instead of refusing. So the FULL gate is
|
||||
(refuse vs. valet-accept), set by the operator per site. Valet is a manned-mode feature with its
|
||||
own custody/session shape — see [[valet-overcapacity]] (deferred).
|
||||
|
||||
## As-built (2026-06-16)
|
||||
|
||||
- **Occupancy** = `occupancyCount` (`apps/server/src/occupancy.ts`): a fold over the ledger —
|
||||
entries minus exits per identity, count those `> 0`. `getOccupancy` returns `{count, capacity,
|
||||
free, full}`.
|
||||
- **Capacity** is a single-row `site_config` table (admin-set; `null` = uncapped). Routes
|
||||
(`routes/site.ts`): `GET /api/occupancy` + `GET /api/site-config` (any role), `PUT /api/site-config`
|
||||
(admin; non-negative int or null).
|
||||
- **FULL gate** is in the **transient entry flow**: `occupancy.full` → refuse (no ticket, no
|
||||
`vehicle_entry`, no open) + signed `anomaly`. **Permit entry is NOT gated** here — subscribers are
|
||||
admitted past transient-full (their own `maxConcurrent` still applies); occupancy can read
|
||||
over-capacity (`free` negative) when permits enter a full lot, as intended.
|
||||
- **UI** `SiteSettings`: live occupancy + FULL badge (everyone); capacity editor (admin).
|
||||
- Verified: fill to cap → 3rd transient refused; permit still admitted past full; exit frees a
|
||||
slot; RBAC (operator can't set capacity); verifyChain ok. Physical FULL-sign relay output is
|
||||
**deferred** (needs a sign device).
|
||||
|
||||
## Open
|
||||
|
||||
- Zone/level granularity at launch vs. single capacity number.
|
||||
- Reserve-for-permits threshold.
|
||||
- Reserve-for-permits **threshold** (a soft transient cap below the hard capacity) — currently
|
||||
permits are simply ungated; a tunable threshold is the richer version.
|
||||
- Physical FULL-sign relay output (a sign-device role).
|
||||
- The valet over-capacity mode + custody model ([[valet-overcapacity]]).
|
||||
|
||||
Reference in New Issue
Block a user