Team & Access
Smart-lock provider abstraction
All smart-lock operations (Nuki, 2N, future providers) route through one unified LockProvider facade. Operator-visible behaviour is unchanged; the abstraction lets new lock vendors plug in cleanly and makes error handling consistent.
What the operator sees (unchanged)
- Smart Locks screen shows one card per property with a lock configured.
- Codes editor + audit log + activity log per property.
- Same buttons, same behaviour — Generate PIN, Revoke, Test Connection.
What changed underneath
- One API — every lock call (generate code, revoke code, unlock, get activity) goes through
lockOps.<operation>with the property's provider auto-resolved. - Retry policy consistent — 5 attempts on transient failures across ALL lock calls (operator-directive decision §D-5). No more silent Nuki 5xx failures (fixed in.
- Never-silent-error — every lock error surfaces on the operator screen + writes an app_errors row. Fixed the class where Nuki 5xx were caught + dropped without notifying operator.
- Adds new providers cleanly — adding a new lock vendor requires implementing the
LockProviderinterface + registering in the provider registry. Four steps, documented indocs/lock-integration-api.md.
Why this matters for the operator
- Error visibility — when a Nuki call fails, the operator now sees it. Earlier a transient 5xx got Sentry'd + the code silently didn't provision. Now the failure surfaces + a retry runs.
- Consistent behaviour across providers — same retry, same error UX regardless of Nuki vs 2N vs a future provider.
- New provider path — if a tenant wants to onboard a new lock brand (Salto, August, TTLock, …), the platform can add it without rewriting every callsite.
What's still per-provider
- Provider capabilities — each provider declares what it supports (
generateCode,revokeCode,unlock,getActivity). Not every provider supports every capability. The V28 Smart Locks screen shows disabled controls with a tooltip when the property's provider doesn't support the operation (mirrors the Channel sync capability-matrix pattern). - Provider-specific config — Nuki API token vs 2N intercom IP address vs whatever the new provider needs. Configured per-property in Settings.
2N lifecycle: dispose
2N sessions require an explicit dispose call to release resources on the intercom side. The abstraction handles this automatically — no operator step needed. Earlier callers forgot to dispose + 2N sessions leaked over time. Fixed.
No-direct-import ratchet
A CI ratchet (server/tests/ratchets/noDirectLockClientImportRatchet.test.ts) fails PR if any file imports Nuki or 2N clients directly instead of going through lockOps. Prevents drift back to the pre-
Source: the FlatsBratislava operator manual.