Door epic phases 4-5: inbound access-log ingest + universal API-call buttons

Phase 4 (migration 053): pull entry/denied events into door_access_log and
match each card to a member, via two transports sharing one ingest core.
Poll: a CRON_SECRET-guarded /api/cron/door-ingest reads each opted-in
native-HeatSync connection's ?z log through the existing hardened executor
and parses it (firmware-characterized, best-effort ring buffer). Webhook:
/api/door/inbound/[connection] accepts pushed event JSON authenticated by a
per-connection bearer secret from the vault. Both dedupe on
(connection_id, dedupe_key) so re-polls and retries are no-ops. Adds an
inbound config panel to /door/manage.

Phase 5 (migration 054): admins define api_buttons that fire a configured
HTTP call (full verbs + headers + body) when an authorized member presses
them, through the same hardened egress (now shared between callDoor and the
new callApi). Each button carries a required_permission (default the new
apicall.invoke code); members see only buttons they may press. Builder at
/door/buttons with a door-template preset; members press from /doors. Every
press is recorded in api_call_log with secrets redacted.

Both subsystems are inert until an admin enables inbound on a connection or
creates a button. 624 unit + 50 integration tests; migrations applied
top-to-bottom in schema.sql; types and docs updated.
This commit is contained in:
Moheeb Zara
2026-05-20 20:45:44 -07:00
parent 6a892cbad5
commit 7ec90b0f1e
33 changed files with 2277 additions and 95 deletions
+18 -6
View File
@@ -155,15 +155,15 @@ export type Database = {
Relationships: []
}
door_connections: {
Row: { id: string; space_id: string; name: string; adapter: string; base_url: string; pinned_host: string; auth_mode: string; auth_param: string | null; secret_ref: string | null; verbs: Json; allow_member_self_entry: boolean; is_enabled: boolean; created_by: string | null; created_at: string; updated_at: string }
Insert: { id?: string; space_id: string; name: string; adapter?: string; base_url: string; pinned_host: string; auth_mode?: string; auth_param?: string | null; secret_ref?: string | null; verbs?: Json; allow_member_self_entry?: boolean; is_enabled?: boolean; created_by?: string | null; created_at?: string; updated_at?: string }
Update: { id?: string; space_id?: string; name?: string; adapter?: string; base_url?: string; pinned_host?: string; auth_mode?: string; auth_param?: string | null; secret_ref?: string | null; verbs?: Json; allow_member_self_entry?: boolean; is_enabled?: boolean; created_by?: string | null; created_at?: string; updated_at?: string }
Row: { id: string; space_id: string; name: string; adapter: string; base_url: string; pinned_host: string; auth_mode: string; auth_param: string | null; secret_ref: string | null; verbs: Json; allow_member_self_entry: boolean; is_enabled: boolean; inbound_enabled: boolean; inbound_secret_ref: string | null; created_by: string | null; created_at: string; updated_at: string }
Insert: { id?: string; space_id: string; name: string; adapter?: string; base_url: string; pinned_host: string; auth_mode?: string; auth_param?: string | null; secret_ref?: string | null; verbs?: Json; allow_member_self_entry?: boolean; is_enabled?: boolean; inbound_enabled?: boolean; inbound_secret_ref?: string | null; created_by?: string | null; created_at?: string; updated_at?: string }
Update: { id?: string; space_id?: string; name?: string; adapter?: string; base_url?: string; pinned_host?: string; auth_mode?: string; auth_param?: string | null; secret_ref?: string | null; verbs?: Json; allow_member_self_entry?: boolean; is_enabled?: boolean; inbound_enabled?: boolean; inbound_secret_ref?: string | null; created_by?: string | null; created_at?: string; updated_at?: string }
Relationships: []
}
door_access_log: {
Row: { id: string; space_id: string; connection_id: string | null; actor_member_id: string | null; target_member_id: string | null; action: string; success: boolean; detail: string | null; occurred_at: string }
Insert: { id?: string; space_id: string; connection_id?: string | null; actor_member_id?: string | null; target_member_id?: string | null; action: string; success?: boolean; detail?: string | null; occurred_at?: string }
Update: { id?: string; space_id?: string; connection_id?: string | null; actor_member_id?: string | null; target_member_id?: string | null; action?: string; success?: boolean; detail?: string | null; occurred_at?: string }
Row: { id: string; space_id: string; connection_id: string | null; actor_member_id: string | null; target_member_id: string | null; action: string; success: boolean; detail: string | null; dedupe_key: string | null; occurred_at: string }
Insert: { id?: string; space_id: string; connection_id?: string | null; actor_member_id?: string | null; target_member_id?: string | null; action: string; success?: boolean; detail?: string | null; dedupe_key?: string | null; occurred_at?: string }
Update: { id?: string; space_id?: string; connection_id?: string | null; actor_member_id?: string | null; target_member_id?: string | null; action?: string; success?: boolean; detail?: string | null; dedupe_key?: string | null; occurred_at?: string }
Relationships: []
}
door_card_slots: {
@@ -172,6 +172,18 @@ export type Database = {
Update: { id?: string; space_id?: string; connection_id?: string; card_id?: string; slot?: number; created_by?: string | null; created_at?: string; updated_at?: string }
Relationships: []
}
api_buttons: {
Row: { id: string; space_id: string; label: string; button_group: string; sort_order: number; method: string; base_url: string; pinned_host: string; url_template: string | null; headers: Json; body_template: string | null; auth_mode: string; auth_param: string | null; secret_ref: string | null; required_permission: string; confirm: boolean; is_enabled: boolean; created_by: string | null; created_at: string; updated_at: string }
Insert: { id?: string; space_id: string; label: string; button_group?: string; sort_order?: number; method?: string; base_url: string; pinned_host: string; url_template?: string | null; headers?: Json; body_template?: string | null; auth_mode?: string; auth_param?: string | null; secret_ref?: string | null; required_permission?: string; confirm?: boolean; is_enabled?: boolean; created_by?: string | null; created_at?: string; updated_at?: string }
Update: { id?: string; space_id?: string; label?: string; button_group?: string; sort_order?: number; method?: string; base_url?: string; pinned_host?: string; url_template?: string | null; headers?: Json; body_template?: string | null; auth_mode?: string; auth_param?: string | null; secret_ref?: string | null; required_permission?: string; confirm?: boolean; is_enabled?: boolean; created_by?: string | null; created_at?: string; updated_at?: string }
Relationships: []
}
api_call_log: {
Row: { id: string; space_id: string; button_id: string | null; actor_member_id: string | null; action: string; success: boolean; detail: string | null; occurred_at: string }
Insert: { id?: string; space_id: string; button_id?: string | null; actor_member_id?: string | null; action: string; success?: boolean; detail?: string | null; occurred_at?: string }
Update: { id?: string; space_id?: string; button_id?: string | null; actor_member_id?: string | null; action?: string; success?: boolean; detail?: string | null; occurred_at?: string }
Relationships: []
}
space_visits: {
Row: { id: string; space_id: string; member_id: string; checked_in_at: string; checked_out_at: string | null; is_host: boolean; check_in_note: string | null; check_out_note: string | null; created_at: string }
Insert: { id?: string; space_id: string; member_id: string; checked_in_at?: string; checked_out_at?: string | null; is_host?: boolean; check_in_note?: string | null; check_out_note?: string | null; created_at?: string }