@osqd/bothandlerjs 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/state.d.ts CHANGED
@@ -153,7 +153,7 @@ export declare class ActorRegistry {
153
153
  * A read, and only a read: it neither records a request against an actor nor moves
154
154
  * one up the LRU, so watching the list cannot change what it lists.
155
155
  */
156
- top(limit: number, now: number): ActorSummary[];
156
+ top(limit: number, now: number, offset?: number): ActorSummary[];
157
157
  forget(key: string): void;
158
158
  clear(): void;
159
159
  /** Marks an actor as holding valid human clearance until `until`. */
@@ -41,6 +41,18 @@ individual name does not.
41
41
  | `library` | curl, wget, python-requests, Go-http-client, okhttp | usually challenge |
42
42
  | `headless` | HeadlessChrome, Playwright, Puppeteer, Selenium | usually challenge |
43
43
  | `embedded` | Smart TVs, set-top boxes, game consoles | allow |
44
+ | `commerce` | idealo, Kelkoo, PriceRunner, Trivago, Skyscanner, Indeedbot | a commercial decision |
45
+ | `academic` | Crossref, OpenAlex, university web-science crawls | usually allow |
46
+ | `accessibility` | Siteimprove and other WCAG auditors | allow |
47
+
48
+ The last three are separate from their nearest neighbours because the decision is. A price
49
+ comparator is not an SEO auditor — nothing there is auditing your site for you, and the
50
+ same crawler is a distribution channel to one retailer and a competitor's research tool to
51
+ the next, which is why nothing in `commerce` is marked benign. A citation index is not a
52
+ model being trained, and an operator refusing the second is often glad of the first. And an
53
+ accessibility crawler is not an uptime probe: it is usually commissioned by the site's own
54
+ owner and then forgotten about, so blocking it does not reduce load, it makes an
55
+ accessibility report look clean by removing the evidence.
44
56
 
45
57
  ```ts
46
58
  { id: "no-ai", match: { category: "ai" }, action: "block", reason: "Not for model training." }
@@ -54,6 +66,7 @@ What, if anything, can check the claim:
54
66
  type Verification =
55
67
  | { kind: "fcrdns"; domains: readonly string[] } // reverse DNS, forward-confirmed
56
68
  | { kind: "ip-ranges"; publishedAt?: string } // an address list the operator publishes
69
+ | { kind: "proof"; via: string } // something only you can check
57
70
  | { kind: "none" }; // no published mechanism
58
71
  ```
59
72
 
@@ -62,6 +75,29 @@ them, and for those the claim is **unfalsifiable**. The library neither confirms
62
75
  accuses — it records what the client said and lets the policy decide what a self-declared
63
76
  identity is worth. See [verifying a crawler](verification.md).
64
77
 
78
+ **But you may be able to check what this library cannot.** Your CDN has often already
79
+ verified the crawler and says so in a header it adds; some bots now sign their requests;
80
+ you may hold ASN data. None of those belong inside a detection library — two need a
81
+ network dependency and the third needs a key it has no business fetching — so instead you
82
+ supply the answer:
83
+
84
+ ```ts
85
+ new BotHandler({
86
+ crawlerVerification: {
87
+ verifiers: {
88
+ // Whatever you can prove, however you prove it.
89
+ gptbot: (ctx) => (ctx.facts.headers["cf-verified-bot"] === "gptbot" ? "verified" : "unknown"),
90
+ },
91
+ },
92
+ });
93
+ ```
94
+
95
+ A verifier returns `"verified"`, `"refuted"` or `"unknown"`, runs before the built-in check
96
+ for that signature, and a definite answer settles it — which also means no DNS lookup.
97
+ `"unknown"` falls through to whatever the library can do on its own, and so does a throw:
98
+ an unreachable key server must never read as an accusation, or an outage becomes a wave of
99
+ blocked crawlers.
100
+
65
101
  ## What a name is worth
66
102
 
67
103
  A matched signature makes `self-identified` produce `certain` evidence, and this is the
@@ -29,6 +29,12 @@ User-Agent, the verdict, the score, the action and the rule that chose it. Filte
29
29
  proven, suspected, human, guard stops, denied, mitigated or served; or type into the
30
30
  search box.
31
31
 
32
+ The feed pages, fifty requests at a time. The newest page follows the stream; stepping
33
+ back holds the list still while you read it — a feed that renumbers itself under somebody
34
+ paging through it cannot be read — and says **held while you read** so the stillness is
35
+ not mistaken for quiet. Requests keep arriving and keep being counted; they are at the
36
+ front when you return to it.
37
+
32
38
  The search takes fields, and negation, because the two cases people actually reach for
33
39
  it are an address that also appears inside a User-Agent and a path that is a prefix of
34
40
  ten others:
@@ -108,6 +114,17 @@ regularity (near zero is a metronome, which no person is), prior confirmations,
108
114
  whether they hold clearance. **In feed** sends one to the live feed as an `actor:` filter,
109
115
  which makes it a shareable URL like every other view.
110
116
 
117
+ This screen pages too, twenty-five at a time, busiest first. The registry holds far more
118
+ clients than the feed's ring holds requests, and paging is what reaches them: the feed
119
+ already shows you what is loudest, and the population behind it is the reason this screen
120
+ exists. Ranking something that is still moving means a client can shift between pages
121
+ while you read; the order is a snapshot of a live list, not a stable index.
122
+
123
+ The **Actors tracked** counter above the tab strip is the way in: it is a button, so
124
+ pressing it — or reaching it with the keyboard and pressing Enter — opens this screen.
125
+ Where the `registry` section is switched off there is no Actors screen to open, and the
126
+ counter stays an ordinary tile rather than offering to go somewhere that does not exist.
127
+
111
128
  **Statistics** — a traffic timeline (1m/5m/15m/1h) split by outcome, which says how
112
129
  much history the window actually holds rather than drawing a flat line through time it
113
130
  never had; the assessment-latency histogram with mean, p95 and max; and the **score
@@ -383,6 +400,15 @@ capped at `maxEventsPerSecond` (100 by default, `0` to remove it). What is cappe
383
400
  still see every request, and the feed says how many were not streamed. A thinned feed
384
401
  must never look like a quiet one.
385
402
 
403
+ **And it does not stop at saying so.** Next to that count is **Load them**, which fetches
404
+ the ring and merges what is missing back into the feed, in the order it happened. Nothing
405
+ was ever lost — the cap and the lag guard both keep entries off the *stream* and leave the
406
+ ring alone — so this is the same data arriving by a different road. It is a button rather
407
+ than something automatic on purpose: entries are skipped exactly when the origin is
408
+ busiest, and a dashboard that answered every skip by re-fetching the whole ring would be a
409
+ load amplifier pointed at the process it is watching, which is what the cap exists to
410
+ prevent.
411
+
386
412
  **A viewer that stops reading is not allowed to cost you memory.** A socket that has
387
413
  stopped draining — a laptop that slept with the tab open, a phone in a tunnel, a proxy
388
414
  that stopped reading — used to accumulate frames in this process, one queue per viewer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osqd/bothandlerjs",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },