New listMyDoorActivity (service client after requireMember; rows where
the caller is actor or target; detail already redacted at write). /doors
composes the existing self-entry control, the masked own cards
(getMyCards, last4 only), and the member's own recent door activity;
empty-state when none. Sidebar 'Doors' link added under Learn.
selfEntry({connectionId}): any active member with >=1 active card on file
may trigger a momentary open on a connection that is enabled and has
allow_member_self_entry on (locked rule: any active card, no slot row
required). Membership/cards resolved server-side so a member can only open
for themselves; never unlock/lock/grant/revoke; never anonymous. Strict
5/min per-member rate limit, one redacted self_entry audit row (denials
audited too), through the existing hardened executor. listSelfEntryDoors
returns the enabled self-entry connections only when the caller has an
active card, so the dashboard 'Door access' panel is hidden for
ineligible members. API_REFERENCE + ARCHITECTURE + HANDOFF pass-28.
listDoorCards (door.operate, service client, masked last4 only) lists
active cards with the slot each holds on a connection. /door/manage now
passes canOperate (door.operate rpc) and, when set, shows per-connection
Open/Unlock/Lock and an expandable card list with Grant/Revoke. Every
live action is behind a confirm; grant shows the allocated slot.
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.
Migration 035 adds door_connections (per-space controller integration:
secret_ref -> AES-256-GCM secrets vault, never a plaintext password;
pinned_host SSRF pin; native_heatsync|generic_http adapter;
allow_member_self_entry opt-in for phase 3) and door_access_log
(append-only, secrets redacted, service-client-only writes; immutable).
RLS additive/default-deny: connections CRUD = door.manage; log SELECT =
door.manage/operate.
Safety core is pure and heavily unit-tested (door-logic, suite 409):
validateDoorTarget (request host must equal the pinned host;
metadata/link-local always blocked; http(s) only), the
firmware-verified HeatSync encoders with fixed-width zero-padding
(slot 3 / perm 3 / tag 8 / 4-char password), generic template
substitution, and redactDoorSecrets. lib/door/executor.ts is the single
hardened egress: re-validates the pin, refuses redirects, caps time and
body, redacts before any audit write. The door password is read from the
secrets vault and decrypted server-side only, never returned or logged.
/door/manage (door.manage) configures connections, picks a vault secret,
and runs a safe status-only test. Sidebar Admin link added; "Learn"
moved before "People" per request. Docs updated.