Files
hackerspace.sh/scripts/053_door_inbound_ingest.sql
T
Moheeb Zara 7ec90b0f1e 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.
2026-05-20 20:45:44 -07:00

56 lines
3.0 KiB
SQL

-- =============================================================================
-- 053: Door inbound access-log ingest (Door epic, phase 4)
-- =============================================================================
-- Phase 4 pulls real entry/denied events FROM a controller INTO door_access_log,
-- via two transports that share one ingest core:
-- * poll: a CRON_SECRET-guarded route reads each enabled connection's log
-- verb (HeatSync ?z) through the existing hardened executor and parses it;
-- * webhook: a per-connection public endpoint accepts normalized event JSON
-- pushed by the controller or a relay, authenticated by a per-connection
-- bearer secret kept in the AES-256-GCM vault.
--
-- This migration is ADDITIVE ONLY (no table re-create, no policy weakening):
--
-- door_access_log.dedupe_key
-- A per-event idempotency token so a re-poll of the same ring-buffer slot
-- or a webhook retry cannot double-insert. Partial-unique on
-- (connection_id, dedupe_key) WHERE dedupe_key IS NOT NULL: existing
-- action rows (grant/revoke/open/self_entry) leave it NULL and are
-- unaffected; only ingested rows set it. The insert path uses ON CONFLICT
-- DO NOTHING against this index.
--
-- door_connections.inbound_enabled (default false; opt-in, like
-- allow_member_self_entry) gates the webhook endpoint for a connection.
-- door_connections.inbound_secret_ref references the SAME secrets vault as
-- secret_ref but holds the INBOUND webhook secret (a distinct credential
-- from the outbound door password). It is loaded + compared server-side
-- only and never returned to the browser.
--
-- PGRST embed note (the migration-048 outage class): door_connections now has
-- two FKs to public.secrets (secret_ref + inbound_secret_ref). This does NOT
-- create the ambiguous-junction shape that broke the auth embed -- that only
-- happens when a table's PRIMARY KEY covers both FK columns; door_connections
-- has a surrogate `id` PK. Nothing embeds secrets through PostgREST anyway
-- (secrets are read service-side by id), and the auth path
-- (space_members <-> spaces) is untouched. Safe.
--
-- No new permission codes: door.manage configures inbound; the cron + webhook
-- writers are the validated service client (door_access_log already has no
-- client INSERT policy and stays immutable from the client).
--
-- Idempotent: ADD COLUMN IF NOT EXISTS, CREATE INDEX IF NOT EXISTS.
-- =============================================================================
ALTER TABLE public.door_access_log
ADD COLUMN IF NOT EXISTS dedupe_key text;
CREATE UNIQUE INDEX IF NOT EXISTS idx_door_access_log_dedupe
ON public.door_access_log (connection_id, dedupe_key)
WHERE dedupe_key IS NOT NULL;
ALTER TABLE public.door_connections
ADD COLUMN IF NOT EXISTS inbound_enabled boolean NOT NULL DEFAULT false;
ALTER TABLE public.door_connections
ADD COLUMN IF NOT EXISTS inbound_secret_ref uuid REFERENCES public.secrets(id) ON DELETE SET NULL;