Skip to content

Commit 3aab303

Browse files
authored
chore(bots): regenerate registry for xAI-SearchBot and LinkupBot (#39)
* feat(sdk): OpenTelemetry spans, without depending on OpenTelemetry A span around protect() and a child around rule evaluation, carrying the decision id (which joins a span to its dashboard row), the conclusion, the deciding rule, and whether the request cost a round trip to ingest. The tracer is INJECTED, not imported. An optional peer dependency was the obvious route and the wrong one: this package is dependency-free and passes an edge-compatibility gate, and Workers and Vercel Edge are exactly where a stray transitive import hurts. A conditional import('@opentelemetry/api') bundles badly too -- the bundler either resolves it, adding weight for the majority who do not use it, or fails on a module that is legitimately absent. The Tracer type is a structural subset of OpenTelemetry's, so trace.getTracer('webdecoy') satisfies it with no adapter. Omit it and there are no spans, no dependency and no behaviour change, which is the majority case and should cost them nothing. A tracer cannot break a request. startSpan returning nothing, throwing, or returning a span whose every method throws all degrade to a no-op -- observability that can take the request path down is worse than none. The no-op span also means call sites need no null checks, and an `if (span)` is a branch that gets forgotten on the path that mattered. Eight tests, four of them hostile tracers. Also asserts the span is ended on the ERROR path: a leaked span holds memory and never reaches the exporter, so the trace is silently incomplete rather than absent. Closes WebDecoy/app#740 * chore(bots): regenerate registry for xAI-SearchBot and LinkupBot Companion to WebDecoy/app. Both crawlers were hitting production and landing unclassified, so requests from them were excluded from AI traffic figures rather than attributed. Generated, not hand-edited: pkg is the source of truth and the export runs in agents.MatchUserAgent order, which the TS matcher depends on because it takes the first hit. The registry count guard moves 169 -> 171. It exists to make a registry change deliberate rather than incidental, so bumping it here is the intended workflow rather than a nuisance. 397 tests pass, parity vectors included. * chore(bots): regenerate registry for the five newly identified crawlers Companion to WebDecoy/app. DuckAssistBot, SofyaBot, Reflectionbot and IbouBot join xAI-SearchBot and LinkupBot; all six were crawling production and landing unclassified. IbouBot is registered as a SEARCH crawler, not AI: its own page states it is a Cloudflare Verified Bot classified as Search. It arrived in the same unclassified tail as the others and does not belong with them. Count guard 171 -> 175. 397 tests pass.
1 parent 1975cb5 commit 3aab303

3 files changed

Lines changed: 80 additions & 2 deletions

File tree

‎packages/webdecoy/src/bots/bots.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ describe('matchUserAgent', () => {
6060

6161
describe('registry integrity', () => {
6262
it('carries the full generated table', () => {
63-
expect(BOT_REGISTRY.length).toBe(169);
63+
expect(BOT_REGISTRY.length).toBe(175);
6464
expect(BOT_CATEGORIES).toContain('training_crawler');
6565
expect(BOT_CATEGORIES).toContain('search_crawler');
6666
});

‎packages/webdecoy/src/bots/parity-vectors.generated.json‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,18 @@
575575
"category": "seo_crawler",
576576
"matched": true
577577
},
578+
{
579+
"userAgent": "duckassistbot",
580+
"id": "duckassistbot",
581+
"category": "ai_search_crawler",
582+
"matched": true
583+
},
584+
{
585+
"userAgent": "Mozilla/5.0 (compatible; duckassistbot/1.0; +http://example.com/bot)",
586+
"id": "duckassistbot",
587+
"category": "ai_search_crawler",
588+
"matched": true
589+
},
578590
{
579591
"userAgent": "duckduckbot",
580592
"id": "duckduckbot",
@@ -935,6 +947,18 @@
935947
"category": "training_crawler",
936948
"matched": true
937949
},
950+
{
951+
"userAgent": "iboubot",
952+
"id": "iboubot",
953+
"category": "search_crawler",
954+
"matched": true
955+
},
956+
{
957+
"userAgent": "Mozilla/5.0 (compatible; iboubot/1.0; +http://example.com/bot)",
958+
"id": "iboubot",
959+
"category": "search_crawler",
960+
"matched": true
961+
},
938962
{
939963
"userAgent": "iframely",
940964
"id": "iframely",
@@ -1055,6 +1079,18 @@
10551079
"category": "fetcher",
10561080
"matched": true
10571081
},
1082+
{
1083+
"userAgent": "linkupbot",
1084+
"id": "linkupbot",
1085+
"category": "ai_search_crawler",
1086+
"matched": true
1087+
},
1088+
{
1089+
"userAgent": "Mozilla/5.0 (compatible; linkupbot/1.0; +http://example.com/bot)",
1090+
"id": "linkupbot",
1091+
"category": "ai_search_crawler",
1092+
"matched": true
1093+
},
10581094
{
10591095
"userAgent": "mj12bot",
10601096
"id": "mj12bot",
@@ -1559,6 +1595,18 @@
15591595
"category": "fetcher",
15601596
"matched": true
15611597
},
1598+
{
1599+
"userAgent": "reflectionbot",
1600+
"id": "reflectionbot",
1601+
"category": "training_crawler",
1602+
"matched": true
1603+
},
1604+
{
1605+
"userAgent": "Mozilla/5.0 (compatible; reflectionbot/1.0; +http://example.com/bot)",
1606+
"id": "reflectionbot",
1607+
"category": "training_crawler",
1608+
"matched": true
1609+
},
15621610
{
15631611
"userAgent": "rogerbot",
15641612
"id": "rogerbot",
@@ -1787,6 +1835,18 @@
17871835
"category": "fetcher",
17881836
"matched": true
17891837
},
1838+
{
1839+
"userAgent": "sofyabot",
1840+
"id": "sofyabot",
1841+
"category": "ai_search_crawler",
1842+
"matched": true
1843+
},
1844+
{
1845+
"userAgent": "Mozilla/5.0 (compatible; sofyabot/1.0; +http://example.com/bot)",
1846+
"id": "sofyabot",
1847+
"category": "ai_search_crawler",
1848+
"matched": true
1849+
},
17901850
{
17911851
"userAgent": "sogou",
17921852
"id": "sogou",
@@ -2147,6 +2207,18 @@
21472207
"category": "generic_scraper",
21482208
"matched": true
21492209
},
2210+
{
2211+
"userAgent": "xai-searchbot",
2212+
"id": "xai-searchbot",
2213+
"category": "ai_search_crawler",
2214+
"matched": true
2215+
},
2216+
{
2217+
"userAgent": "Mozilla/5.0 (compatible; xai-searchbot/1.0; +http://example.com/bot)",
2218+
"id": "xai-searchbot",
2219+
"category": "ai_search_crawler",
2220+
"matched": true
2221+
},
21502222
{
21512223
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
21522224
"matched": false

‎packages/webdecoy/src/bots/registry.generated.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* agents share User-Agent substrings, so sorting this array changes how real
1212
* traffic is classified.
1313
*
14-
* 169 agents across 15 categories.
14+
* 175 agents across 15 categories.
1515
*/
1616

1717
/**
@@ -75,6 +75,7 @@ export const BOT_CATEGORIES: readonly BotCategory[] = [
7575

7676
/** The agent table, in match order. */
7777
export const BOT_REGISTRY: readonly BotAgent[] = [
78+
{ id: "reflectionbot", name: "Reflectionbot", category: "training_crawler", organization: "Reflection AI", baseScore: 70, respectsRobots: true, uaPatterns: ["reflectionbot"] },
7879
{ id: "gptbot", name: "GPTBot", category: "training_crawler", organization: "OpenAI", baseScore: 85, respectsRobots: true, uaPatterns: ["gptbot"] },
7980
{ id: "chatgpt-user", name: "ChatGPT-User", category: "training_crawler", organization: "OpenAI", baseScore: 85, respectsRobots: true, uaPatterns: ["chatgpt-user", "chatgpt"] },
8081
{ id: "oai-searchbot", name: "OAI-SearchBot", category: "training_crawler", organization: "OpenAI", baseScore: 80, respectsRobots: true, uaPatterns: ["oai-searchbot"] },
@@ -103,6 +104,10 @@ export const BOT_REGISTRY: readonly BotAgent[] = [
103104
{ id: "velenpublicwebcrawler", name: "VelenPublicWebCrawler", category: "training_crawler", organization: "Velen", baseScore: 70, respectsRobots: false, uaPatterns: ["velenpublicwebcrawler"] },
104105
{ id: "isscyberriskcrawler", name: "ISSCyberRiskCrawler", category: "training_crawler", organization: "ISS", baseScore: 65, respectsRobots: false, uaPatterns: ["isscyberriskcrawler"] },
105106
{ id: "friendlycrawler", name: "FriendlyCrawler", category: "training_crawler", organization: "Unknown", baseScore: 65, respectsRobots: true, uaPatterns: ["friendlycrawler"] },
107+
{ id: "duckassistbot", name: "DuckAssistBot", category: "ai_search_crawler", organization: "DuckDuckGo", baseScore: 65, respectsRobots: true, uaPatterns: ["duckassistbot"] },
108+
{ id: "sofyabot", name: "SofyaBot", category: "ai_search_crawler", organization: "Sofya", baseScore: 65, respectsRobots: true, uaPatterns: ["sofyabot"] },
109+
{ id: "xai-searchbot", name: "xAI-SearchBot", category: "ai_search_crawler", organization: "xAI", baseScore: 70, respectsRobots: true, uaPatterns: ["xai-searchbot"] },
110+
{ id: "linkupbot", name: "LinkupBot", category: "ai_search_crawler", organization: "Linkup", baseScore: 65, respectsRobots: true, uaPatterns: ["linkupbot"] },
106111
{ id: "searchgpt", name: "SearchGPT", category: "ai_search_crawler", organization: "OpenAI", baseScore: 75, respectsRobots: true, uaPatterns: ["searchgpt"] },
107112
{ id: "phind", name: "Phind", category: "ai_search_crawler", organization: "Phind", baseScore: 70, respectsRobots: true, uaPatterns: ["phind"] },
108113
{ id: "kagi", name: "Kagi", category: "ai_search_crawler", organization: "Kagi", baseScore: 65, respectsRobots: true, uaPatterns: ["kagi"] },
@@ -119,6 +124,7 @@ export const BOT_REGISTRY: readonly BotAgent[] = [
119124
{ id: "browser-use", name: "Browser-Use", category: "ai_agent", organization: "Open Source", baseScore: 70, respectsRobots: false, uaPatterns: ["browser-use"] },
120125
{ id: "stagehand", name: "Stagehand", category: "ai_agent", organization: "Browserbase", baseScore: 70, respectsRobots: false, uaPatterns: ["stagehand"] },
121126
{ id: "multion", name: "MultiOn", category: "ai_agent", organization: "MultiOn", baseScore: 70, respectsRobots: false, uaPatterns: ["multion"] },
127+
{ id: "iboubot", name: "IbouBot", category: "search_crawler", organization: "Ibou", baseScore: 30, respectsRobots: true, uaPatterns: ["iboubot"] },
122128
{ id: "googlebot", name: "Googlebot", category: "search_crawler", organization: "Google", baseScore: 30, respectsRobots: true, uaPatterns: ["googlebot"] },
123129
{ id: "bingbot", name: "Bingbot", category: "search_crawler", organization: "Microsoft", baseScore: 30, respectsRobots: true, uaPatterns: ["bingbot"] },
124130
{ id: "yandexbot", name: "YandexBot", category: "search_crawler", organization: "Yandex", baseScore: 35, respectsRobots: true, uaPatterns: ["yandexbot"] },

0 commit comments

Comments
 (0)