@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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/index.cjs +1278 -0
  4. package/dist/index.js +1240 -0
  5. package/dist/types/cjs/client.d.ts +362 -0
  6. package/dist/types/cjs/contracts/access.d.ts +166 -0
  7. package/dist/types/cjs/contracts/actions.d.ts +191 -0
  8. package/dist/types/cjs/contracts/api.d.ts +891 -0
  9. package/dist/types/cjs/contracts/benchmark.d.ts +116 -0
  10. package/dist/types/cjs/contracts/checkpoint.d.ts +164 -0
  11. package/dist/types/cjs/contracts/compliance.d.ts +412 -0
  12. package/dist/types/cjs/contracts/crawl.d.ts +302 -0
  13. package/dist/types/cjs/contracts/delivery.d.ts +136 -0
  14. package/dist/types/cjs/contracts/evidenceRecord.d.ts +192 -0
  15. package/dist/types/cjs/contracts/execution.d.ts +197 -0
  16. package/dist/types/cjs/contracts/extractor.d.ts +379 -0
  17. package/dist/types/cjs/contracts/file.d.ts +117 -0
  18. package/dist/types/cjs/contracts/firecrawl.d.ts +258 -0
  19. package/dist/types/cjs/contracts/groundTruth.d.ts +77 -0
  20. package/dist/types/cjs/contracts/identityBundle.d.ts +70 -0
  21. package/dist/types/cjs/contracts/index.d.ts +30 -0
  22. package/dist/types/cjs/contracts/map.d.ts +180 -0
  23. package/dist/types/cjs/contracts/monitor.d.ts +217 -0
  24. package/dist/types/cjs/contracts/monitorConfig.d.ts +9 -0
  25. package/dist/types/cjs/contracts/policy.d.ts +93 -0
  26. package/dist/types/cjs/contracts/proxy.d.ts +52 -0
  27. package/dist/types/cjs/contracts/recipe.d.ts +74 -0
  28. package/dist/types/cjs/contracts/regexSafety.d.ts +31 -0
  29. package/dist/types/cjs/contracts/result.d.ts +503 -0
  30. package/dist/types/cjs/contracts/session.d.ts +51 -0
  31. package/dist/types/cjs/contracts/ssrf.d.ts +16 -0
  32. package/dist/types/cjs/contracts/status.d.ts +27 -0
  33. package/dist/types/cjs/contracts/structured.d.ts +185 -0
  34. package/dist/types/cjs/contracts/tableMarkdown.d.ts +58 -0
  35. package/dist/types/cjs/contracts/tokens.d.ts +47 -0
  36. package/dist/types/cjs/index.d.ts +9 -0
  37. package/dist/types/cjs/package.json +1 -0
  38. package/dist/types/cjs/version.d.ts +8 -0
  39. package/dist/types/cjs/watcher.d.ts +151 -0
  40. package/dist/types/esm/client.d.ts +362 -0
  41. package/dist/types/esm/contracts/access.d.ts +166 -0
  42. package/dist/types/esm/contracts/actions.d.ts +191 -0
  43. package/dist/types/esm/contracts/api.d.ts +891 -0
  44. package/dist/types/esm/contracts/benchmark.d.ts +116 -0
  45. package/dist/types/esm/contracts/checkpoint.d.ts +164 -0
  46. package/dist/types/esm/contracts/compliance.d.ts +412 -0
  47. package/dist/types/esm/contracts/crawl.d.ts +302 -0
  48. package/dist/types/esm/contracts/delivery.d.ts +136 -0
  49. package/dist/types/esm/contracts/evidenceRecord.d.ts +192 -0
  50. package/dist/types/esm/contracts/execution.d.ts +197 -0
  51. package/dist/types/esm/contracts/extractor.d.ts +379 -0
  52. package/dist/types/esm/contracts/file.d.ts +117 -0
  53. package/dist/types/esm/contracts/firecrawl.d.ts +258 -0
  54. package/dist/types/esm/contracts/groundTruth.d.ts +77 -0
  55. package/dist/types/esm/contracts/identityBundle.d.ts +70 -0
  56. package/dist/types/esm/contracts/index.d.ts +30 -0
  57. package/dist/types/esm/contracts/map.d.ts +180 -0
  58. package/dist/types/esm/contracts/monitor.d.ts +217 -0
  59. package/dist/types/esm/contracts/monitorConfig.d.ts +9 -0
  60. package/dist/types/esm/contracts/policy.d.ts +93 -0
  61. package/dist/types/esm/contracts/proxy.d.ts +52 -0
  62. package/dist/types/esm/contracts/recipe.d.ts +74 -0
  63. package/dist/types/esm/contracts/regexSafety.d.ts +31 -0
  64. package/dist/types/esm/contracts/result.d.ts +503 -0
  65. package/dist/types/esm/contracts/session.d.ts +51 -0
  66. package/dist/types/esm/contracts/ssrf.d.ts +16 -0
  67. package/dist/types/esm/contracts/status.d.ts +27 -0
  68. package/dist/types/esm/contracts/structured.d.ts +185 -0
  69. package/dist/types/esm/contracts/tableMarkdown.d.ts +58 -0
  70. package/dist/types/esm/contracts/tokens.d.ts +47 -0
  71. package/dist/types/esm/index.d.ts +9 -0
  72. package/dist/types/esm/version.d.ts +8 -0
  73. package/dist/types/esm/watcher.d.ts +151 -0
  74. 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