feat(door): P3-C live grant/revoke/open-lock-unlock actions
door.operate, rate-limited, every attempt writes one redacted door_access_log row. Operators have no RLS read on door_connections/ member_cards so reads use the service client after the permission check, space-scoped. grantCard reserves the slot in the DB first (unique constraints arbitrate concurrency), calls the controller, rolls the reservation back on failure; idempotent re-grant; slot_exhausted when full. revokeCard idempotent; frees the slot only on confirmed controller success so the app map never diverges from the device. doorControl open/unlock/lock via the single hardened executor. API_REFERENCE.
This commit is contained in:
@@ -946,6 +946,25 @@ export const doorConnectionIdSchema = z.object({
|
||||
connectionId: z.string().uuid('Invalid connection ID'),
|
||||
})
|
||||
|
||||
// ─── Door live actions (door.operate) ────────────────────────────────────────
|
||||
|
||||
export const doorGrantSchema = z.object({
|
||||
connectionId: z.string().uuid('Invalid connection ID'),
|
||||
cardId: z.string().uuid('Invalid card ID'),
|
||||
// HeatSync permission mask; defaults to 1 (basic access).
|
||||
permissionMask: z.number().int().min(0).max(255).optional().default(1),
|
||||
})
|
||||
|
||||
export const doorRevokeSchema = z.object({
|
||||
connectionId: z.string().uuid('Invalid connection ID'),
|
||||
cardId: z.string().uuid('Invalid card ID'),
|
||||
})
|
||||
|
||||
export const doorControlSchema = z.object({
|
||||
connectionId: z.string().uuid('Invalid connection ID'),
|
||||
verb: z.enum(['open', 'unlock', 'lock']),
|
||||
})
|
||||
|
||||
// ─── Generic ID schemas ──────────────────────────────────────────────────────
|
||||
|
||||
export const uuidSchema = z.string().uuid('Invalid ID format')
|
||||
|
||||
Reference in New Issue
Block a user