/** * DamcrawlerCitationVerifier — verifies each already-cited URL is live and * carries the target brand, via a gated damcrawler scrape (in-house-ai- * visibility, Sprint 4; * arch-20260717-in-house-oss-ai-visibility-architecture.md:163-174,253,283). * * Mirrors `DamcrawlerSerpProvider`'s guard-first/lazy-load/never-throw shape * (`./damcrawler-serp-provider.ts:69-106`) with two differences: the network * call is a per-URL `scrape()` (not `search()`), and there is a THIRD guard * — `assertSafeUrl` per url, AFTER `load()`, BEFORE any damcrawler network * call — mirroring `DamcrawlerCrawlEngine`'s SSRF guard (F2, * `./damcrawler-crawl-engine.ts:156,191-192,227`). This class only fetches * URLs a `MentionCitationExtractor` already surfaced as candidates — it does * NOT scrape answer-engine UIs (that is `ScrapeArmEngineProvider`, Sprint * 10, a nonGoal here). * * `verify()` is fail-closed on every axis (sc-4-2): axis off, damcrawler * absent, a per-url SSRF rejection, or a scrape error/`.error` result all * degrade that url to `{ live: false, brandOnPage: false }` — this class * NEVER fabricates a live citation and NEVER throws to its caller. It * returns exactly one `VerifiedCitation` per input url, in input order. * * damcrawler's `ScrapeResult` (confirmed against * `/Users/bober4ik/damcrawler/src/commands/scrape.ts:99-125`) exposes NO * numeric HTTP status field — the batch-mode `error?` string is the only * failure signal, so `live` here means "scrape succeeded without an * `.error`", not literally "returned HTTP 200". * * Per ADR-11, the scraped `title`/`markdown` are attacker-controlled * free-text and are sanitized HERE, at the network->in-process boundary, * via `ContentSanitizer(dam.sanitize)` — mirrors every other damcrawler * adapter (`damcrawler-crawl-engine.ts`, `damcrawler-serp-provider.ts`). * `brandOnPage` is computed over the SANITIZED body only. */ import type { SeoEgressGuard } from "../egress.js"; /** One verified (or fail-closed-unverified) citation URL. */ export type VerifiedCitation = { url: string; live: boolean; brandOnPage: boolean; }; export interface CitationVerifier { verify(target: string, urls: string[]): Promise; } /** * NARROW local view of the ONLY damcrawler surface this verifier calls — * confirmed against the real damcrawler source (sprint briefing §1): * - `scrape(urls, options)` — `damcrawler/src/commands/scrape.ts:99-125`; * batch-mode result rows carry `{url, title, markdown, error?}`; there is * NO numeric HTTP status field, `error?` is the only failure signal. * - `sanitize(raw, options?)` — the ONLY damcrawler sanitize export that * yields `hadThreats` (`damcrawler/src/lib/sanitize.ts:68-79,89`); * `sanitizeWithReport` returns a bare `string` and must NOT be used * here (mirrors every other damcrawler adapter in this directory). * - `assertSafeUrl(urlString)` — damcrawler's own SSRF guard * (`damcrawler/src/index.ts:259`), called per url AFTER `load()`, * BEFORE any damcrawler network call (F2, mirrors * `damcrawler-crawl-engine.ts:156,191-192,227`). * Defined locally (not imported from the real dep) so tests never need the * real `damcrawler` package installed. */ interface DamcrawlerVerifyModule { scrape(urls: string[], options: { formats?: string[]; }): Promise>; sanitize(raw: string, options?: { sourceUrl?: string; }): { content: string; hadThreats: boolean; }; assertSafeUrl(urlString: string): Promise; } /** Loader seam — the default performs the lazy dynamic import; tests inject a FAKE module (or `undefined` to simulate the dep being absent). */ export type DamcrawlerVerifyLoader = () => Promise; /** * `CitationVerifier` backed by damcrawler. Guard-first, lazy-load, per-url * SSRF-guarded, sanitize-at-boundary, never-throw; returns exactly one * `VerifiedCitation` per input url, in order. */ export declare class DamcrawlerCitationVerifier implements CitationVerifier { private readonly egress; private readonly load; constructor(egress: SeoEgressGuard, load?: DamcrawlerVerifyLoader); verify(target: string, urls: string[]): Promise; /** Verifies a single url: SSRF guard, then scrape+sanitize+brand-match. Never throws — fail-closed on any error. */ private verifyOne; } export {}; //# sourceMappingURL=citation-verifier.d.ts.map