playbook/antigravity-awesome-skills/plugins/antigravity-awesome-skills-.../skills/vercel-optimize/lib/gates/platform-bot-protection.mjs

116 lines
5.0 KiB
JavaScript

// Recommend BotID only when there's EVIDENCE of bot traffic or scale large enough that the rec is defensible.
// Without an evidence gate the rec fires on quiet hobby sites and erodes trust.
const MIN_BOT_PCT = 0.05;
const MIN_EDGE_COST = 25; // halved for 14d window
const MIN_TOTAL_REQUESTS = 14_000; // ~14k/14d matches the prior 30k/30d rate
const MIN_TOTAL_FDT_BYTES = 1_000_000;
export const metadata = {
id: 'platform_bot_protection',
threshold: 'botIdEnabled=false AND (botPct >= 0.05 OR edge_cost >= $25/window OR requests >= 14k/14d)',
billingDimension: 'edge-requests',
scope: 'account',
sourceCitation: 'vercel-optimize gate threshold',
description:
'When BotID is disabled AND there is evidence (observed bot bandwidth share, edge cost, or substantial request volume) that bot traffic is non-trivial. Bot traffic inflates edge request counts without delivering user value; staged bot protection can reduce waste on bot-heavy projects. Skipped on quiet projects with no bot evidence — the recommendation would be noise.',
};
export function gate(signals) {
// BotID surfaces under several legacy fields; check all.
const botEnabled =
signals.project?.security?.botIdEnabled === true
|| signals.project?.security?.botProtection === true
|| signals.project?.botProtection?.enabled === true
|| signals.project?.delegatedProtection?.bot === true;
if (botEnabled) return [];
// Project config failed — we can't tell if BotID is on, so stay silent.
if (signals.project?.error) return [];
const totalRequests = totalRequestsFromSignals(signals);
const botShare = computeBotShare(signals);
const edgeService = (signals.usage?.services ?? []).find(
(s) => /edge.request/i.test(s.name ?? '')
);
const edgeCost = edgeService?.billedCost ?? null;
// Require observable bot share, edge cost, OR substantial traffic — otherwise rec is just config nagging.
const hasObservedBots = botShare?.botPct != null && botShare.botPct >= MIN_BOT_PCT;
const hasMaterialEdgeCost = edgeCost != null && edgeCost >= MIN_EDGE_COST;
const hasSubstantialTraffic = totalRequests >= MIN_TOTAL_REQUESTS;
if (!hasObservedBots && !hasMaterialEdgeCost && !hasSubstantialTraffic) return [];
const challengeRule = signals.project?.security?.managedRules?.bot_filter;
const ruleNote = challengeRule?.active
? `firewall bot_filter rule active (action=${challengeRule.action ?? '?'})`
: 'no firewall bot_filter rule';
// Kicker on high observed bot share — harder evidence than config alone.
let priority = edgeCost != null ? Math.max(20, Math.round(edgeCost)) : 30;
if (botShare?.botPct != null && botShare.botPct > 0.2) priority += 20;
// Confidence bumps when we can SEE bot traffic, not just infer from config.
let confidence = edgeCost != null ? 0.85 : 0.6;
if (botShare?.botPct != null && botShare.botPct > 0.2) confidence = Math.min(0.95, confidence + 0.05);
const botShareNote = botShare?.botPct != null
? `bot_fdt_pct=${(botShare.botPct * 100).toFixed(0)}%`
: 'bot_fdt_pct=unknown';
return [{
kind: metadata.id,
scope: 'account',
files: [],
priority,
confidence,
o11ySignal: edgeCost != null
? `edge_cost=${edgeCost.toFixed(0)},bot_protection=disabled,${botShareNote},${ruleNote}`
: `requests=${totalRequests},bot_protection=disabled,${botShareNote},${ruleNote}`,
reason: botShare?.botPct != null && botShare.botPct > 0.2
? 'BotID disabled with observable bot bandwidth share'
: 'BotID disabled with observable traffic',
question: botShare?.botPct != null && botShare.botPct > 0.2
? `Bot traffic accounts for ${(botShare.botPct * 100).toFixed(0)}% of FDT bytes (top category: ${botShare.topCategory ?? 'unknown'}). Would enabling BotID + a challenge rule reduce that share?`
: 'Would enabling BotID (Bot Protection) reduce edge request volume from automated traffic?',
evidence: {
botEnabled: false,
edgeCost,
totalRequests,
managedRules: challengeRule ?? null,
botShare: botShare ?? null,
},
}];
}
function totalRequestsFromSignals(signals) {
const rows = signals.metrics?.requestsByRouteCache?.rows;
if (!Array.isArray(rows)) return 0;
return rows.reduce((s, r) => s + (r.value ?? 0), 0);
}
// CLI convention: bot_category="" means "not classified as a bot" (human + unclassified); any non-empty = bot.
function computeBotShare(signals) {
const rows = signals.metrics?.fdtByBot?.rows;
if (!Array.isArray(rows) || rows.length === 0) return null;
let humanBytes = 0;
let botBytes = 0;
let topCategory = null;
let topBytes = 0;
for (const r of rows) {
const v = r.value ?? 0;
const cat = r.bot_category ?? '';
if (cat === '') {
humanBytes += v;
} else {
botBytes += v;
if (v > topBytes) {
topBytes = v;
topCategory = cat;
}
}
}
const total = humanBytes + botBytes;
if (total < MIN_TOTAL_FDT_BYTES) return null;
return { humanBytes, botBytes, botPct: botBytes / total, topCategory };
}