@octocrawl/sdk 0.3.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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/index.cjs +1278 -0
- package/dist/index.js +1240 -0
- package/dist/types/cjs/client.d.ts +362 -0
- package/dist/types/cjs/contracts/access.d.ts +166 -0
- package/dist/types/cjs/contracts/actions.d.ts +191 -0
- package/dist/types/cjs/contracts/api.d.ts +891 -0
- package/dist/types/cjs/contracts/benchmark.d.ts +116 -0
- package/dist/types/cjs/contracts/checkpoint.d.ts +164 -0
- package/dist/types/cjs/contracts/compliance.d.ts +412 -0
- package/dist/types/cjs/contracts/crawl.d.ts +302 -0
- package/dist/types/cjs/contracts/delivery.d.ts +136 -0
- package/dist/types/cjs/contracts/evidenceRecord.d.ts +192 -0
- package/dist/types/cjs/contracts/execution.d.ts +197 -0
- package/dist/types/cjs/contracts/extractor.d.ts +379 -0
- package/dist/types/cjs/contracts/file.d.ts +117 -0
- package/dist/types/cjs/contracts/firecrawl.d.ts +258 -0
- package/dist/types/cjs/contracts/groundTruth.d.ts +77 -0
- package/dist/types/cjs/contracts/identityBundle.d.ts +70 -0
- package/dist/types/cjs/contracts/index.d.ts +30 -0
- package/dist/types/cjs/contracts/map.d.ts +180 -0
- package/dist/types/cjs/contracts/monitor.d.ts +217 -0
- package/dist/types/cjs/contracts/monitorConfig.d.ts +9 -0
- package/dist/types/cjs/contracts/policy.d.ts +93 -0
- package/dist/types/cjs/contracts/proxy.d.ts +52 -0
- package/dist/types/cjs/contracts/recipe.d.ts +74 -0
- package/dist/types/cjs/contracts/regexSafety.d.ts +31 -0
- package/dist/types/cjs/contracts/result.d.ts +503 -0
- package/dist/types/cjs/contracts/session.d.ts +51 -0
- package/dist/types/cjs/contracts/ssrf.d.ts +16 -0
- package/dist/types/cjs/contracts/status.d.ts +27 -0
- package/dist/types/cjs/contracts/structured.d.ts +185 -0
- package/dist/types/cjs/contracts/tableMarkdown.d.ts +58 -0
- package/dist/types/cjs/contracts/tokens.d.ts +47 -0
- package/dist/types/cjs/index.d.ts +9 -0
- package/dist/types/cjs/package.json +1 -0
- package/dist/types/cjs/version.d.ts +8 -0
- package/dist/types/cjs/watcher.d.ts +151 -0
- package/dist/types/esm/client.d.ts +362 -0
- package/dist/types/esm/contracts/access.d.ts +166 -0
- package/dist/types/esm/contracts/actions.d.ts +191 -0
- package/dist/types/esm/contracts/api.d.ts +891 -0
- package/dist/types/esm/contracts/benchmark.d.ts +116 -0
- package/dist/types/esm/contracts/checkpoint.d.ts +164 -0
- package/dist/types/esm/contracts/compliance.d.ts +412 -0
- package/dist/types/esm/contracts/crawl.d.ts +302 -0
- package/dist/types/esm/contracts/delivery.d.ts +136 -0
- package/dist/types/esm/contracts/evidenceRecord.d.ts +192 -0
- package/dist/types/esm/contracts/execution.d.ts +197 -0
- package/dist/types/esm/contracts/extractor.d.ts +379 -0
- package/dist/types/esm/contracts/file.d.ts +117 -0
- package/dist/types/esm/contracts/firecrawl.d.ts +258 -0
- package/dist/types/esm/contracts/groundTruth.d.ts +77 -0
- package/dist/types/esm/contracts/identityBundle.d.ts +70 -0
- package/dist/types/esm/contracts/index.d.ts +30 -0
- package/dist/types/esm/contracts/map.d.ts +180 -0
- package/dist/types/esm/contracts/monitor.d.ts +217 -0
- package/dist/types/esm/contracts/monitorConfig.d.ts +9 -0
- package/dist/types/esm/contracts/policy.d.ts +93 -0
- package/dist/types/esm/contracts/proxy.d.ts +52 -0
- package/dist/types/esm/contracts/recipe.d.ts +74 -0
- package/dist/types/esm/contracts/regexSafety.d.ts +31 -0
- package/dist/types/esm/contracts/result.d.ts +503 -0
- package/dist/types/esm/contracts/session.d.ts +51 -0
- package/dist/types/esm/contracts/ssrf.d.ts +16 -0
- package/dist/types/esm/contracts/status.d.ts +27 -0
- package/dist/types/esm/contracts/structured.d.ts +185 -0
- package/dist/types/esm/contracts/tableMarkdown.d.ts +58 -0
- package/dist/types/esm/contracts/tokens.d.ts +47 -0
- package/dist/types/esm/index.d.ts +9 -0
- package/dist/types/esm/version.d.ts +8 -0
- package/dist/types/esm/watcher.d.ts +151 -0
- package/package.json +40 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Honest-mode identity + provable-compliance record contracts.
|
|
3
|
+
*
|
|
4
|
+
* Two things live here, and they are deliberately separated:
|
|
5
|
+
*
|
|
6
|
+
* 1. `CrawlMode` — the product's mode switch, inverted. Every mode is *true*:
|
|
7
|
+
* it declares who the crawler is and what compliance it promises. There is
|
|
8
|
+
* no stealth mode and no "lie a little" tier; the empirical probe showed
|
|
9
|
+
* the honest arm (aligned client hints) already outperforms the stealth arm
|
|
10
|
+
* (see packages/bench research notes), so a covert mode would carry §1201
|
|
11
|
+
* exposure with no performance upside.
|
|
12
|
+
*
|
|
13
|
+
* 2. `ComplianceRecord` — the premium tier: a tamper-evident, signable record
|
|
14
|
+
* of *what the crawler actually did* (robots.txt decision, the exact
|
|
15
|
+
* headers it sent, the rate-limit facts), so a third party — publisher,
|
|
16
|
+
* enterprise buyer, court — can verify the claim rather than take it on
|
|
17
|
+
* faith. This is the buyer-side attestation cell the market research found
|
|
18
|
+
* empty: TollBit/x402/RSL/Cloudflare all sell to *publishers*; nobody sells
|
|
19
|
+
* the crawler a machine-verifiable record of its own compliance.
|
|
20
|
+
*
|
|
21
|
+
* This package stays types-only (no crypto, no I/O). The signing primitive is
|
|
22
|
+
* an abstract `ComplianceSigner` implemented in http-core or a leaf package;
|
|
23
|
+
* contracts only carries the opaque signature triple.
|
|
24
|
+
*
|
|
25
|
+
* Honesty invariant baked into the type: a record's `mode` is the single source
|
|
26
|
+
* of truth for the *declared* identity, and the `sentHeaders` fact records the
|
|
27
|
+
* bytes actually on the wire. A mismatch between the two is detectable from the
|
|
28
|
+
* signed record alone — which is the point.
|
|
29
|
+
*/
|
|
30
|
+
import type { Lane } from './status.js';
|
|
31
|
+
import type { AccessFact } from './access.js';
|
|
32
|
+
import type { NetworkPolicy } from './policy.js';
|
|
33
|
+
/**
|
|
34
|
+
* The honest-mode switch. `mode` names an identity + compliance policy; the
|
|
35
|
+
* runtime derives a `Lane` (execution tier) and a concrete user-agent from it.
|
|
36
|
+
*/
|
|
37
|
+
export type CrawlMode = 'research' | 'standard' | 'authed' | 'proxy';
|
|
38
|
+
/**
|
|
39
|
+
* The two declared browser identities. `desktop` is the one every browser
|
|
40
|
+
* mode has always sent; `mobile` (`mobile: true` on a request) is a second
|
|
41
|
+
* declared identity, Android Chrome with aligned hints and a phone viewport,
|
|
42
|
+
* that passes the same coherence and honesty checks. Neither is a statement
|
|
43
|
+
* about the host: the desktop identity claims macOS on any machine and the
|
|
44
|
+
* mobile one Android on desktop Chromium, each internally coherent.
|
|
45
|
+
*/
|
|
46
|
+
export type IdentityDevice = 'desktop' | 'mobile';
|
|
47
|
+
/** Canonical UA shape per mode. Values live in http-core/bench (ua.ts), not here. */
|
|
48
|
+
export interface ModeIdentity {
|
|
49
|
+
mode: CrawlMode;
|
|
50
|
+
/**
|
|
51
|
+
* The exact User-Agent string the mode declares. The runtime must send this
|
|
52
|
+
* verbatim — a differing UA is a lie the signed record exposes.
|
|
53
|
+
*/
|
|
54
|
+
userAgent: string;
|
|
55
|
+
/**
|
|
56
|
+
* Client-hint headers aligned to the UA, e.g. sec-ch-ua / sec-ch-ua-platform
|
|
57
|
+
* / sec-ch-ua-mobile. Alignment — not stealth — is what avoids the
|
|
58
|
+
* HeadlessChrome block signal; an inconsistent set is a bug, not a disguise.
|
|
59
|
+
*/
|
|
60
|
+
clientHints: Readonly<Record<string, string>>;
|
|
61
|
+
/**
|
|
62
|
+
* Whether the mode claims to respect robots.txt. All four modes are true:
|
|
63
|
+
* login (`authed`) and egress (`proxy`) do not waive robots. The record
|
|
64
|
+
* captures the actual per-decision facts regardless, so a claim here that
|
|
65
|
+
* the record's `robots` contradicts is a verifiable lie.
|
|
66
|
+
*/
|
|
67
|
+
respectsRobots: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Which of the two declared browser identities this is: the desktop one
|
|
70
|
+
* (macOS Chrome) or the mobile one (Android Chrome, `mobile: true`). Absent
|
|
71
|
+
* on the research identity, which declares a bot, not a device. The lanes
|
|
72
|
+
* record it (`identity_sent.detail.device`, `identity_declared`); the
|
|
73
|
+
* compliance record (schemaVersion 2) has no field for it and carries the
|
|
74
|
+
* device in its as-sent `sentHeaders` instead.
|
|
75
|
+
*/
|
|
76
|
+
device?: IdentityDevice;
|
|
77
|
+
/**
|
|
78
|
+
* The lane this mode resolves to. Modes are policy, lanes are execution:
|
|
79
|
+
* research → browser_local (declared bot identity)
|
|
80
|
+
* standard → browser_local (plain browser consistency)
|
|
81
|
+
* authed → browser_local_authed (owned login state)
|
|
82
|
+
* proxy → browser_proxy (BYO egress; compliance responsibility is the
|
|
83
|
+
* operator's, and the record still captures the facts)
|
|
84
|
+
*/
|
|
85
|
+
lane: Lane;
|
|
86
|
+
}
|
|
87
|
+
export declare const RESEARCH_USER_AGENT = "Mozilla/5.0 (compatible; w2l-research/0.1; +https://github.com/77777R7/Octocrawl; research benchmark, one request per page)";
|
|
88
|
+
/** Longest operator contact (`W2L_CONTACT`) the research User-Agent declares. */
|
|
89
|
+
export declare const MAX_CONTACT_LENGTH = 200;
|
|
90
|
+
/** The name research mode declares to SEC.gov, before the contact, in SEC's `<Company or name> <email>` format. */
|
|
91
|
+
export declare const SEC_DECLARED_NAME = "W2L Research";
|
|
92
|
+
/** The robots.txt product token of both research User-Agent formats (RFC 9309 §2.2.1). */
|
|
93
|
+
export declare const RESEARCH_PRODUCT_TOKEN = "w2l-research";
|
|
94
|
+
/**
|
|
95
|
+
* Whether a host is sec.gov or one of its subdomains. SEC's fair-access
|
|
96
|
+
* policy prescribes the declared User-Agent `<Company or name> <email>`, and
|
|
97
|
+
* SEC.gov answers 403 to the research format even when it declares a contact.
|
|
98
|
+
*/
|
|
99
|
+
export declare function isSecHost(host: string): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* The research-mode User-Agent. With the operator's contact (`W2L_CONTACT`,
|
|
102
|
+
* such as a name and email address or a URL), it ends `; contact: <contact>)`:
|
|
103
|
+
* publishers such as the SEC ask automated clients to declare one. To an SEC
|
|
104
|
+
* host (`host`, see isSecHost) it is SEC's own format instead,
|
|
105
|
+
* `W2L Research <contact>`.
|
|
106
|
+
*/
|
|
107
|
+
export declare function researchUserAgent(contact?: string | null, host?: string | null): string;
|
|
108
|
+
/** The contact a research-mode User-Agent declares, in either format (see researchUserAgent); null for any other User-Agent. */
|
|
109
|
+
export declare function declaredContact(userAgent: string): string | null;
|
|
110
|
+
/** Whether a User-Agent is one research mode declares, in either format. */
|
|
111
|
+
export declare function isResearchUserAgent(userAgent: string): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* The text robots.txt `User-agent` lines are matched against for a
|
|
114
|
+
* User-Agent W2L sends. SEC's format names no product token, so the research
|
|
115
|
+
* token is added: a group for w2l-research governs research requests to
|
|
116
|
+
* SEC.gov as it does on every other host.
|
|
117
|
+
*/
|
|
118
|
+
export declare function robotsAgent(userAgent: string): string;
|
|
119
|
+
/** The operator's contact from `W2L_CONTACT`, trimmed; null when unset or blank. The error never repeats the value. */
|
|
120
|
+
export declare function operatorContact(env: Readonly<Record<string, string | undefined>>): string | null;
|
|
121
|
+
/** An operator policy whose research-mode requests declare `W2L_CONTACT`, when it is set. */
|
|
122
|
+
export declare function withOperatorContact(policy: NetworkPolicy, env: Readonly<Record<string, string | undefined>>): NetworkPolicy;
|
|
123
|
+
/**
|
|
124
|
+
* Floor used when no real browser version is known. Subjects driving real
|
|
125
|
+
* Chromium MUST pass the actual `browser.version()` major instead — declaring
|
|
126
|
+
* a Chrome version you are not running is an inconsistency, not a feature.
|
|
127
|
+
*/
|
|
128
|
+
export declare const CHROME_MAJOR_FLOOR = 128;
|
|
129
|
+
/** Full Chrome UA for a given major, in the shape the probe's D arm used. */
|
|
130
|
+
export declare function browserUserAgent(chromeMajor: number): string;
|
|
131
|
+
/**
|
|
132
|
+
* Client-hint headers aligned to the UA. The sec-ch-ua / sec-ch-ua-mobile /
|
|
133
|
+
* sec-ch-ua-platform triple must quote the same major as the UA, and the
|
|
134
|
+
* platform token must match what navigator.platform reports — an unaligned set
|
|
135
|
+
* is the bug, not a disguise.
|
|
136
|
+
*
|
|
137
|
+
* `accept-language` is deliberately NOT here: it is not a client hint, it is
|
|
138
|
+
* a normal header the browser derives from the context `locale`, and Chromium
|
|
139
|
+
* normalizes it (dropping the `;q=` weight). Declaring it as a hint would
|
|
140
|
+
* guarantee a declared-vs-sent mismatch on every fetch — so it stays under
|
|
141
|
+
* `locale`/`BROWSER_FINGERPRINT`, where it is a setting, not a claim.
|
|
142
|
+
*/
|
|
143
|
+
export declare function browserClientHints(chromeMajor: number): Readonly<Record<string, string>>;
|
|
144
|
+
/**
|
|
145
|
+
* The mobile Chrome UA of the second declared identity (`mobile: true`): a
|
|
146
|
+
* Pixel 7 on Android 14, the same Chrome major as the desktop UA. Android on
|
|
147
|
+
* desktop Chromium the way the desktop identity is macOS on any host:
|
|
148
|
+
* internally coherent, not a statement about the machine.
|
|
149
|
+
*/
|
|
150
|
+
export declare function mobileBrowserUserAgent(chromeMajor: number): string;
|
|
151
|
+
/** Client hints aligned to the mobile UA: the same brands and major, `sec-ch-ua-mobile: ?1`, platform Android. */
|
|
152
|
+
export declare function mobileBrowserClientHints(chromeMajor: number): Readonly<Record<string, string>>;
|
|
153
|
+
/**
|
|
154
|
+
* The user-agent metadata Chromium derives its own client hints from
|
|
155
|
+
* (`Emulation.setUserAgentOverride.userAgentMetadata`): the brands of
|
|
156
|
+
* `sec-ch-ua` in the same order, the platform of `sec-ch-ua-platform`, the
|
|
157
|
+
* mobile flag of `sec-ch-ua-mobile`. The browser lane sets it on every page
|
|
158
|
+
* so that the hints Chromium generates itself, on a redirect hop and on the
|
|
159
|
+
* page's own requests, where a context's extra headers do not reach, are the
|
|
160
|
+
* declared ones rather than the headless shell's (`HeadlessChrome`), and so
|
|
161
|
+
* that `navigator.userAgentData` says the same as the wire.
|
|
162
|
+
*/
|
|
163
|
+
export interface BrowserUserAgentMetadata {
|
|
164
|
+
brands: readonly {
|
|
165
|
+
brand: string;
|
|
166
|
+
version: string;
|
|
167
|
+
}[];
|
|
168
|
+
fullVersionList: readonly {
|
|
169
|
+
brand: string;
|
|
170
|
+
version: string;
|
|
171
|
+
}[];
|
|
172
|
+
platform: string;
|
|
173
|
+
platformVersion: string;
|
|
174
|
+
architecture: string;
|
|
175
|
+
model: string;
|
|
176
|
+
mobile: boolean;
|
|
177
|
+
}
|
|
178
|
+
/** The metadata behind the declared identity's client hints (browserClientHints, mobileBrowserClientHints), for a Chrome major and device. */
|
|
179
|
+
export declare function browserUserAgentMetadata(chromeMajor: number, device?: IdentityDevice): BrowserUserAgentMetadata;
|
|
180
|
+
/** The `sec-ch-ua` value Chromium serializes from `brands`, so the metadata and the declared hint can be compared. */
|
|
181
|
+
export declare function serializeBrands(brands: readonly {
|
|
182
|
+
brand: string;
|
|
183
|
+
version: string;
|
|
184
|
+
}[]): string;
|
|
185
|
+
/**
|
|
186
|
+
* Fingerprint context fields that must match the UA for a consistent browser
|
|
187
|
+
* identity: applied to the Playwright context by the subject, kept here so
|
|
188
|
+
* the values are single-sourced with the UA rather than drifted per subject.
|
|
189
|
+
*/
|
|
190
|
+
export interface BrowserFingerprint {
|
|
191
|
+
locale: string;
|
|
192
|
+
timezoneId: string;
|
|
193
|
+
viewport: {
|
|
194
|
+
width: number;
|
|
195
|
+
height: number;
|
|
196
|
+
};
|
|
197
|
+
screen: {
|
|
198
|
+
width: number;
|
|
199
|
+
height: number;
|
|
200
|
+
};
|
|
201
|
+
deviceScaleFactor: number;
|
|
202
|
+
/** Whether the context reports a mobile device (`navigator.maxTouchPoints`, the viewport meta); false for the desktop identity. */
|
|
203
|
+
isMobile: boolean;
|
|
204
|
+
hasTouch: boolean;
|
|
205
|
+
}
|
|
206
|
+
/** The desktop identity's fingerprint. */
|
|
207
|
+
export declare const BROWSER_FINGERPRINT: Readonly<BrowserFingerprint>;
|
|
208
|
+
/** The mobile identity's fingerprint: a 412x915 phone viewport at 2.625 device pixels per CSS pixel, touch, the same locale and time zone. */
|
|
209
|
+
export declare const MOBILE_BROWSER_FINGERPRINT: Readonly<BrowserFingerprint>;
|
|
210
|
+
/** The fingerprint of a declared browser identity; the desktop one for an identity that declares no device (research). */
|
|
211
|
+
export declare function browserFingerprintFor(device: IdentityDevice | undefined): Readonly<BrowserFingerprint>;
|
|
212
|
+
/**
|
|
213
|
+
* The identity for a mode. `standard`, `authed`, and `proxy` share one
|
|
214
|
+
* consistent-browser identity (they differ only in execution lane — session,
|
|
215
|
+
* egress), the desktop one unless `device` asks for the mobile one;
|
|
216
|
+
* `research` is the declared bot with no client hints and no device,
|
|
217
|
+
* declaring the operator's `contact` when there is one, in the format the
|
|
218
|
+
* page's `host` asks for (see researchUserAgent).
|
|
219
|
+
*/
|
|
220
|
+
export declare function modeIdentity(mode: CrawlMode, chromeMajor?: number, contact?: string | null, host?: string | null, device?: IdentityDevice): ModeIdentity;
|
|
221
|
+
/**
|
|
222
|
+
* The product token the hosted public preview adds to the standard
|
|
223
|
+
* User-Agent, so a site owner can see the preview in their logs and address
|
|
224
|
+
* it in robots.txt with `User-agent: octocrawl-preview` (or `octocrawl`).
|
|
225
|
+
* Groups match by substring of the whole User-Agent (http-core
|
|
226
|
+
* matchRobotsGroup); `*` still applies when no group names it. The token used
|
|
227
|
+
* to be `W2L-Preview/1.0`, so a group for `w2l-preview` or `w2l` no longer
|
|
228
|
+
* governs the preview.
|
|
229
|
+
*/
|
|
230
|
+
export declare const PREVIEW_PRODUCT_TOKEN = "OctoCrawl-Preview/1.0 (+https://octocrawl.dev)";
|
|
231
|
+
/**
|
|
232
|
+
* The hosted preview's identity: the standard identity with
|
|
233
|
+
* PREVIEW_PRODUCT_TOKEN appended. The client hints stay as they are: they
|
|
234
|
+
* describe the Chrome that sends the request, and the token names no browser.
|
|
235
|
+
* Only the standard identity takes the token.
|
|
236
|
+
*/
|
|
237
|
+
export declare function previewIdentity(identity: ModeIdentity): ModeIdentity;
|
|
238
|
+
/** All four identities, for the subject layer to enumerate without a switch. */
|
|
239
|
+
export declare const MODE_IDENTITIES: Readonly<Record<CrawlMode, ModeIdentity>>;
|
|
240
|
+
/** Why robots.txt could not be fetched: a 5xx, a network failure, or the lookup's own deadline. */
|
|
241
|
+
export type RobotsUnreachable = 'server_error' | 'network_error' | 'timeout';
|
|
242
|
+
/**
|
|
243
|
+
* A caller's recorded decision to fetch one URL although its host's
|
|
244
|
+
* robots.txt disallows it: a researcher fetching a report the publisher links
|
|
245
|
+
* publicly from a CDN host whose rules address crawlers. It is never a blanket
|
|
246
|
+
* switch; it names one URL, carries a reason, and everything about it (the
|
|
247
|
+
* rule it set aside, the reason, who recorded it) goes into the trace, the
|
|
248
|
+
* result's warnings and, in a lane that mints one, the compliance record, so
|
|
249
|
+
* the fetch stays citable.
|
|
250
|
+
*/
|
|
251
|
+
export interface RobotsOverride {
|
|
252
|
+
/** Why this URL may be fetched despite the rule, in the caller's words. */
|
|
253
|
+
reason: string;
|
|
254
|
+
/** Who recorded the decision, when the caller wants that on the record. */
|
|
255
|
+
recordedBy?: string;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The outcome of consulting robots.txt for a single target URL. One record per
|
|
259
|
+
* fetch. `consulted` distinguishes "we checked and it said X" from "there was
|
|
260
|
+
* nothing to check" — a record that skips the check must say so, never pretend.
|
|
261
|
+
*/
|
|
262
|
+
export interface RobotsDecision {
|
|
263
|
+
/** The robots.txt URL consulted, e.g. `https://site.example/robots.txt`. */
|
|
264
|
+
robotsUrl: string | null;
|
|
265
|
+
/** sha256 of the robots.txt bytes actually parsed, for drift verification. */
|
|
266
|
+
robotsSha256: string | null;
|
|
267
|
+
/**
|
|
268
|
+
* Which user-agent group matched. Null when robots.txt was absent or had no
|
|
269
|
+
* group for this UA — recorded as a fact, not an assumption.
|
|
270
|
+
*/
|
|
271
|
+
matchedUserAgentGroup: string | null;
|
|
272
|
+
/** The compiled rules that fired for this path, most-specific first. */
|
|
273
|
+
appliedRules: readonly {
|
|
274
|
+
pattern: string;
|
|
275
|
+
allow: boolean;
|
|
276
|
+
}[];
|
|
277
|
+
/** Final decision: allowed, disallowed, or no-robots (nothing consulted). */
|
|
278
|
+
decision: 'allowed' | 'disallowed' | 'no_robots';
|
|
279
|
+
/** When disallowed, whether the fetch was skipped because of it. */
|
|
280
|
+
skippedFetch: boolean;
|
|
281
|
+
crawlDelayMs?: number | null;
|
|
282
|
+
/**
|
|
283
|
+
* Set only when robots.txt could not be fetched. RFC 9309 §2.3.1.4 then
|
|
284
|
+
* requires assuming a complete disallow: `decision` is `disallowed` with no
|
|
285
|
+
* rules and no robots.txt hash, and this reason tells it apart from a
|
|
286
|
+
* disallow the publisher wrote. A 4xx is not unreachable: it means no
|
|
287
|
+
* robots.txt, and `decision` is `no_robots`.
|
|
288
|
+
*/
|
|
289
|
+
unreachable?: RobotsUnreachable;
|
|
290
|
+
/**
|
|
291
|
+
* Present when a disallow the publisher wrote was set aside by a recorded
|
|
292
|
+
* decision: the fetch went ahead (`skippedFetch: false`) and this says on
|
|
293
|
+
* whose word. Never set for an unreachable robots.txt.
|
|
294
|
+
*/
|
|
295
|
+
override?: RobotsOverride;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* What actually went on the wire. Sorted by header name, lowercased names.
|
|
299
|
+
* Captured as-sent — including any header that would contradict the mode.
|
|
300
|
+
*/
|
|
301
|
+
export interface SentHeadersFact {
|
|
302
|
+
/** Exact request headers, lowercased names, sorted. Empty when not captured. */
|
|
303
|
+
headers: readonly {
|
|
304
|
+
name: string;
|
|
305
|
+
value: string;
|
|
306
|
+
}[];
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* The rate-limit facts for this fetch relative to the preceding fetch to the
|
|
310
|
+
* same host. The record asserts the *measured* facts; non-compliance is
|
|
311
|
+
* recorded honestly as `compliant: false`, never omitted.
|
|
312
|
+
*/
|
|
313
|
+
export interface RateLimitFact {
|
|
314
|
+
/** Same-host previous request timestamp, epoch ms. Null on first request. */
|
|
315
|
+
previousRequestAtMs: number | null;
|
|
316
|
+
/** Delay actually observed before this request, ms. Null on first request. */
|
|
317
|
+
observedDelayMs: number | null;
|
|
318
|
+
/** The policy minimum delay for this host at the time. */
|
|
319
|
+
requiredDelayMs: number;
|
|
320
|
+
/** True iff observedDelayMs >= requiredDelayMs (or first request). */
|
|
321
|
+
compliant: boolean;
|
|
322
|
+
/** Requests to this host within the last second, for burst verification. */
|
|
323
|
+
recentSameHostCount: number;
|
|
324
|
+
}
|
|
325
|
+
/** A single fetch's compliance record. Tamper-evident via the content hash. */
|
|
326
|
+
export interface ComplianceRecord {
|
|
327
|
+
/** Schema version, bumped on breaking shape change. v2 added `access`. */
|
|
328
|
+
schemaVersion: 2;
|
|
329
|
+
/** Opaque id, unique per fetch. */
|
|
330
|
+
recordId: string;
|
|
331
|
+
/** The mode under which the fetch ran. Bind's the declared identity. */
|
|
332
|
+
mode: CrawlMode;
|
|
333
|
+
/** The URL that was requested (pre-redirect). */
|
|
334
|
+
requestedUrl: string;
|
|
335
|
+
/** Final URL after redirects; null if the fetch never completed. */
|
|
336
|
+
finalUrl: string | null;
|
|
337
|
+
/** ISO timestamp of the request. */
|
|
338
|
+
requestedAt: string;
|
|
339
|
+
robots: RobotsDecision;
|
|
340
|
+
sentHeaders: SentHeadersFact;
|
|
341
|
+
rateLimit: RateLimitFact;
|
|
342
|
+
/**
|
|
343
|
+
* Whose network and whose session this fetch used, and who accepted
|
|
344
|
+
* responsibility for that. Credential-free by construction (see access.ts:
|
|
345
|
+
* proxy passwords and cookie values appear only as hashes). Always present —
|
|
346
|
+
* operator-owned access is stated explicitly, because "we did not record
|
|
347
|
+
* this" and "this was ours" are different claims.
|
|
348
|
+
*/
|
|
349
|
+
access: AccessFact;
|
|
350
|
+
/**
|
|
351
|
+
* Hash of the previous record in the run's chain, hex. Null for the first
|
|
352
|
+
* record. Chaining makes deletion or reordering of a run's history evident.
|
|
353
|
+
*/
|
|
354
|
+
prevRecordHash: string | null;
|
|
355
|
+
/** sha256 of the canonical serialization of everything above. */
|
|
356
|
+
contentHash: string;
|
|
357
|
+
/**
|
|
358
|
+
* Opaque signature triple, produced by a `ComplianceSigner`. Absent until a
|
|
359
|
+
* signer is configured — an unsigned record is still a record, just not a
|
|
360
|
+
* verifiable one. contracts does not import crypto; the signer lives in a
|
|
361
|
+
* leaf package.
|
|
362
|
+
*/
|
|
363
|
+
signature: {
|
|
364
|
+
scheme: string;
|
|
365
|
+
keyId: string;
|
|
366
|
+
value: string;
|
|
367
|
+
} | null;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* A whole run's compliance ledger: the ordered chain of per-fetch records.
|
|
371
|
+
* `records[i].prevRecordHash` must equal `records[i-1].contentHash`.
|
|
372
|
+
*/
|
|
373
|
+
export interface ComplianceLedger {
|
|
374
|
+
runId: string;
|
|
375
|
+
/** The mode policy in effect for this run, for record-set verification. */
|
|
376
|
+
mode: CrawlMode;
|
|
377
|
+
records: readonly ComplianceRecord[];
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Abstract signer. Implemented where keys live (leaf package); `contracts`
|
|
381
|
+
* stays crypto-free so this interface is the seam, not a dependency.
|
|
382
|
+
*/
|
|
383
|
+
export interface ComplianceSigner {
|
|
384
|
+
readonly scheme: string;
|
|
385
|
+
readonly keyId: string;
|
|
386
|
+
/** Produce a signature over `contentHash` (hex). */
|
|
387
|
+
sign(contentHash: string): Promise<{
|
|
388
|
+
value: string;
|
|
389
|
+
}>;
|
|
390
|
+
/** Verify a signature produced by a possibly-different signer. */
|
|
391
|
+
verify(contentHash: string, signature: string): Promise<boolean>;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Whether the identity a mode *declared* matches the headers that were
|
|
395
|
+
* actually sent. This is the load-bearing check: a record that declares
|
|
396
|
+
* `userAgent: chromeUA` while the wire carried the default `HeadlessChrome`
|
|
397
|
+
* UA is a lie, and the whole point of the record is that such a lie is
|
|
398
|
+
* detectable from the signed bytes alone.
|
|
399
|
+
*/
|
|
400
|
+
export interface HonestyVerdict {
|
|
401
|
+
honest: boolean;
|
|
402
|
+
/** Human-readable mismatches, empty when honest. Never silent on a miss. */
|
|
403
|
+
mismatches: readonly string[];
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Compare a declared identity against the actual sent headers. `sentHeaders`
|
|
407
|
+
* is the as-sent fact the record already carries; the declared `userAgent` and
|
|
408
|
+
* `clientHints` come from the mode. A mismatch is reported, not papered over —
|
|
409
|
+
* the fix is to align the context, not to widen the check.
|
|
410
|
+
*/
|
|
411
|
+
export declare function checkIdentityHonesty(identity: ModeIdentity, sent: SentHeadersFact): HonestyVerdict;
|
|
412
|
+
//# sourceMappingURL=compliance.d.ts.map
|