Skip to content

TESA: re-resolve live state in modify/revoke (auto-activation + start change) - #22

Merged
jesusVMayor merged 2 commits into
mainfrom
feat/tesa-resolve-real-state
Jun 19, 2026
Merged

jesusVMayor merged 2 commits into
mainfrom
feat/tesa-resolve-real-state

Conversation

@jesusVMayor

Copy link
Copy Markdown
Member

Why

TESA Smartair grants carry a precheckin flag frozen at creation time, but Smartair auto-activates a pre-assignment into a check-in when its activation time passes. The frozen flag then lies, and both modify and revoke broke against a real server:

  • modify kept calling precheckinModifyDate on a preAssignationId that no longer existed → [700] ERROR_BAD_PARAMETERS (and that call was also missing the required roomId).
  • revoke had the same stale-flag bug.

A second, broader gap: Smartair's *ModifyDate ops only move the expiration, never the activation. So any change to the start date (most visibly: a delayed arrival turning a live check-in back into a future pre-assignment) could not be expressed by an in-place modify.

What

1. Re-resolve live state (b50a317). modify/revoke now read the room's live state (findAllRooms) and match it back to the grant by its PIN — Smartair returns the active keyPad, unique among live credentials — before acting:

  • our preAssignationId still pending → precheckin ops (now sends roomId too)
  • room occupied and its keyPad equals our PIN → check-in ops
  • anything else → gone: modify raises LockNotFoundError, revoke no-ops

"Gone" is the safe default: we never touch a stay we can't prove is ours (cancelled elsewhere, or a room taken over by another guest).

2. Recreate on start change (7780eb9). When the requested start differs from what Smartair holds, modify revokes + recreates the grant reusing the same PIN (guest credential unchanged) and returns a fresh ref. A pure expiration change keeps the cheap in-place path. The trigger avoids a recreate loop (a now/past start on an already-active check-in needs no recreate).

Contract change (additive, backward-compatible)

modify_access/revoke_access gain an optional pin=None: the only durable handle tying a check-in back to its grant. Other providers (TTLock/Omnitec/Salto) accept and ignore it — they keep stable ref handles and can move the start in place, so they have no stale-flag problem. The PIN is never written to the opaque ref.

Tests

48 TESA tests (incl. the exact bug scenario, owner-takeover, the delayed-arrival recreate, and same-activation no-recreate) + 58 base/other-provider tests. All green.

Validated end-to-end against a real Smartair server (modify of an activated pre-assignment, and the delayed-arrival checkin→precheckin recreate reusing the PIN).

Smartair auto-activates a pre-assignment into a check-in when its
activation time passes. The grant ref froze precheckin=true at creation,
so modify kept calling precheckinModifyDate on a preAssignationId that no
longer exists -> [700] ERROR_BAD_PARAMETERS (and precheckinModifyDate also
requires roomId, which was omitted). Revoke had the same stale-flag bug.

modify/revoke now re-read the room's live state (findAllRooms) and match
it back to the grant before acting:
- our preAssignationId still pending -> precheckin ops (now sends roomId too)
- room occupied and its keyPad equals our PIN -> check-in ops
- anything else -> "gone": modify raises LockNotFoundError, revoke no-ops

The PIN is the only durable handle tying a check-in back to its grant
(Smartair returns it as keyPad, unique among live credentials), so the
base contract gains an additive pin=None on modify_access/revoke_access.
Other providers accept and ignore it (stable ref handles, no stale-flag
problem). The PIN is never written to the opaque ref.

"Gone" is the safe default: we never touch a stay we cannot prove is ours,
so a pre-assignment cancelled elsewhere or a room taken over by another
guest is left alone.

Validated end-to-end against a real Smartair server.
Smartair's checkinModifyDate/precheckinModifyDate move only the expiration;
the activation date is immutable. A modify that changes the start therefore
cannot be expressed in place — most visibly when arrival is delayed and a
live check-in must turn back into a future pre-assignment.

_do_modify_access now detects a start change and, only then, revokes and
recreates the grant reusing the same PIN (so the guest's credential is
unchanged) and returns the fresh ref. A pure expiration change keeps the
cheap in-place ModifyDate path.

The trigger avoids a recreate loop: a now/past start on an already-active
check-in needs no recreate (its historical activation instant is irrelevant
and Smartair pins it to "now"); only a future start on a live check-in, a
now/past start on a still-pending pre-assignment, or a future start whose
pre-assignment activation no longer matches forces the recreate.

This is a TESA peculiarity, kept in the adapter: the base contract stays
vendor-agnostic and other providers (which can move the start in place) are
untouched.
@jesusVMayor
jesusVMayor merged commit 2f63c8d into main Jun 19, 2026
2 checks passed
@jesusVMayor
jesusVMayor deleted the feat/tesa-resolve-real-state branch June 19, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant