-- IHF Phase 9 — External API Surface and Consumer SDKs -- IHUB-WP-0010-T01: api_consumers, api_keys, webhook_subscriptions, -- webhook_deliveries, api_request_log -- api_consumers: external systems that authenticate against /api/v2/ -- hub_capability_manifest_id is set when the consumer is a domain hub; -- NULL for third-party tools that authenticate without a manifest. CREATE TABLE api_consumers ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, name TEXT NOT NULL, description TEXT, hub_capability_manifest_id UUID REFERENCES hub_capability_manifests(id), rate_limit_per_minute INTEGER NOT NULL DEFAULT 60, quota_per_day INTEGER NOT NULL DEFAULT 10000, quota_resets_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT (date_trunc('day', NOW() AT TIME ZONE 'UTC') + interval '1 day'), is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL ); CREATE INDEX api_consumers_manifest_idx ON api_consumers (hub_capability_manifest_id); -- api_keys: bearer tokens for consumer authentication -- key_hash stores SHA-256 hex of the full key; key_prefix (first 8 hex chars) -- is shown in UI for identification. The full key is never stored. -- token_type: 'static' for admin-created keys, 'oauth' for tokens from /api/v2/token CREATE TABLE api_keys ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, api_consumer_id UUID NOT NULL REFERENCES api_consumers(id) ON DELETE CASCADE, key_prefix TEXT NOT NULL, key_hash TEXT NOT NULL, scopes TEXT NOT NULL DEFAULT '', token_type TEXT NOT NULL DEFAULT 'static' CHECK (token_type IN ('static', 'oauth')), expires_at TIMESTAMP WITH TIME ZONE, revoked_at TIMESTAMP WITH TIME ZONE, last_used_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL ); CREATE UNIQUE INDEX api_keys_prefix_idx ON api_keys (key_prefix); CREATE INDEX api_keys_consumer_idx ON api_keys (api_consumer_id); CREATE INDEX api_keys_hash_idx ON api_keys (key_hash); -- webhook_subscriptions: consumer subscriptions to framework lifecycle events. -- event_topic uses framework-level event names (distinct from widget interaction -- event_type_registry which stores user interaction types like 'clicked', 'viewed'). CREATE TABLE webhook_subscriptions ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, api_consumer_id UUID NOT NULL REFERENCES api_consumers(id) ON DELETE CASCADE, event_type TEXT NOT NULL CHECK (event_type IN ( 'interaction_event.created', 'annotation.created', 'requirement_candidate.created', 'decision_record.created', 'deployment_record.created', 'outcome_signal.created' )), target_url TEXT NOT NULL, secret TEXT NOT NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL ); CREATE INDEX webhook_subs_consumer_idx ON webhook_subscriptions (api_consumer_id); CREATE INDEX webhook_subs_event_type_idx ON webhook_subscriptions (event_type); -- webhook_deliveries: delivery attempt log (append-only) CREATE TABLE webhook_deliveries ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, webhook_subscription_id UUID NOT NULL REFERENCES webhook_subscriptions(id), payload JSONB NOT NULL, attempted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL, status TEXT NOT NULL CHECK (status IN ('pending', 'delivered', 'failed')), response_code INTEGER, latency_ms INTEGER, error_message TEXT ); CREATE INDEX webhook_deliveries_sub_idx ON webhook_deliveries (webhook_subscription_id, attempted_at DESC); -- Append-only trigger for webhook_deliveries CREATE OR REPLACE FUNCTION webhook_deliveries_no_update() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'webhook_deliveries is append-only'; END; $$; CREATE TRIGGER webhook_deliveries_no_update BEFORE UPDATE ON webhook_deliveries FOR EACH ROW EXECUTE FUNCTION webhook_deliveries_no_update(); CREATE OR REPLACE FUNCTION webhook_deliveries_no_delete() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'webhook_deliveries is append-only'; END; $$; CREATE TRIGGER webhook_deliveries_no_delete BEFORE DELETE ON webhook_deliveries FOR EACH ROW EXECUTE FUNCTION webhook_deliveries_no_delete(); -- api_request_log: usage tracking for dashboard and rate limiting CREATE TABLE api_request_log ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL, api_consumer_id UUID REFERENCES api_consumers(id), endpoint TEXT NOT NULL, method TEXT NOT NULL, status_code INTEGER NOT NULL, latency_ms INTEGER, requested_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL ); CREATE INDEX api_request_log_consumer_time_idx ON api_request_log (api_consumer_id, requested_at DESC);