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:
2026-06-16 08:13:06 +02:00
parent 644bfa1462
commit e579fe5b6e
13 changed files with 983 additions and 1 deletions
+15
View File
@@ -592,3 +592,18 @@ guarantee. Recorded in [[dingtian-relay]] (new Hardening section).
- VERIFIED: open→double-open 409→payments (cash+card; one dated outside the window excluded)→close
totals (cash 500/card 250/3)→close-again 409→re-open ok; readonly 403; verifyChain ok. Full build 5/5.
- Updated [[shift]] (as-built).
## [2026-06-16] build | Capacity / FULL gate (occupancy fold + transient refuse)
- Occupancy = fold over the ledger (entries−exits per identity; `apps/server/src/occupancy.ts`),
`getOccupancy` → {count, capacity, free, full}. Capacity = single-row `site_config` table (admin,
null=uncapped); migration 0001 (additive, no prompt).
- FULL gate in the TRANSIENT entry flow: occupancy.full → refuse (no ticket/entry/open) + signed
anomaly. Permit entry NOT gated (subscribers admitted past transient-full; their maxConcurrent
still applies) — occupancy can read over-capacity by design.
- Routes (`routes/site.ts`): GET /api/occupancy + GET /api/site-config (any role), PUT
/api/site-config (admin; non-neg int or null). UI `SiteSettings`: live occupancy + FULL badge
(all), capacity editor (admin).
- VERIFIED: fill to cap=2 → 3rd transient refused (anomaly, no open); permit still admitted (occ 3/2,
free −1); exit frees a slot; routes RBAC (op can't set, −5→400, set/clear ok); verifyChain ok.
Full build 5/5. Physical FULL-sign relay output deferred.
- Updated [[capacity-occupancy]] (as-built).