inter-hub-haskell/Application/Migration/1743811200-ihf-phase9-external-api.sql

117 lines
4.9 KiB
MySQL
Raw Normal View History

2026-04-01 19:52:20 +00:00
-- 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);