CREATE TABLE firewall_client ( id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL REFERENCES tenant (id) ON DELETE CASCADE, name TEXT NOT NULL, hostname TEXT, token_prefix TEXT NOT NULL, token_hash BLOB NOT NULL, status TEXT NOT NULL DEFAULT 'pending', enroll_seed_used INTEGER NOT NULL DEFAULT 1, last_seen_at TEXT, last_seen_at_source TEXT, last_seen_ip TEXT, last_apply_at TEXT, last_apply_status TEXT, last_apply_error TEXT, last_apply_prefix_count INTEGER DEFAULT 0, last_apply_ip_count INTEGER DEFAULT 0, last_apply_source TEXT, client_version TEXT, settings_json TEXT NOT NULL DEFAULT '{}', created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), approved_at TEXT, approved_by_api_key_id TEXT, revoked_at TEXT, CHECK (status IN ('pending', 'approved', 'revoked')), CHECK (length(trim(name)) > 0), CHECK (length(token_hash) = 32) ); CREATE UNIQUE INDEX idx_firewall_client_token_hash ON firewall_client (token_hash); CREATE INDEX idx_firewall_client_tenant_status ON firewall_client (tenant_id, status); CREATE INDEX idx_firewall_client_last_seen ON firewall_client (last_seen_at DESC) WHERE status = 'approved'; CREATE TABLE firewall_rule ( id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL REFERENCES tenant (id) ON DELETE CASCADE, client_id TEXT REFERENCES firewall_client (id) ON DELETE CASCADE, priority INTEGER NOT NULL, action TEXT NOT NULL, community_id TEXT REFERENCES bgp_community (id) ON DELETE CASCADE, comment TEXT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), CHECK (action IN ('block', 'accept')), CHECK (priority >= 1 AND priority <= 10000), UNIQUE (tenant_id, client_id, priority) ); CREATE INDEX idx_firewall_rule_tenant_priority ON firewall_rule (tenant_id, priority); CREATE INDEX idx_firewall_rule_client ON firewall_rule (client_id) WHERE client_id IS NOT NULL; CREATE INDEX idx_firewall_rule_tenant_default ON firewall_rule (tenant_id, priority) WHERE client_id IS NULL;