generated from coulomb/repo-seed
Some checks failed
Test / test (push) Has been cancelled
Delivers the full Phase 9 external API layer: - Versioned REST API (/api/v2/) with OpenAPI 3.1 spec; enum arrays for widget_type, event_type, annotation category drawn live from registry tables - OAuth 2.0 client credentials flow (/api/v2/token); hub:*:write scopes gated on active HubCapabilityManifest FK - API key management: SHA256-hashed tokens, key_prefix for display, one-time reveal on creation, revocation support - TypeScript and Python consumer SDKs generated from registry tables (/api/v2/sdk/ihf-client.ts, /api/v2/sdk/ihf-client.py) - Webhook delivery: HMAC-SHA256 signing, append-only webhook_deliveries, fire-and-forget dispatch via forkIO, 3-retry logic - Admin API dashboard with 24h stats (request count, error rate, last seen) - Rate limiting (per-minute) and daily quota enforcement via api_request_log - Schema migration: api_consumers, api_keys, webhook_subscriptions (CHECK constraint on 6 framework lifecycle topics), webhook_deliveries (append-only trigger), api_request_log - ARCHITECTURE-LAYERS.md scorecard: 3.34 → 3.41 (approaching Strong) - contracts/functional/interaction-reporting-v1.md extended with Phase 9 endpoint catalogue and 422 validation error format GAAF: no bare TEXT discriminators; webhook event_type uses CHECK constraint over 6 allowed framework lifecycle topic strings (not widget event types). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
117 lines
4.9 KiB
PL/PgSQL
117 lines
4.9 KiB
PL/PgSQL
-- 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);
|