/** * ScrapeArmEngineProvider — the damcrawler UI-scrape `AiVisibilityProvider` * arm (in-house-ai-visibility, Sprint 10; arch-20260717-in-house-oss-ai- * visibility-architecture.md:101-114,307-310; ADR-4). * * A near-fusion of TWO existing templates, deliberately mirrored rather than * reinvented: * - `DamcrawlerSerpProvider` (`./damcrawler-serp-provider.ts:69-106`) — * guard-first/lazy-load/never-throw shape, and its docstring's rationale * for omitting an `assertSafeUrl` SSRF guard (`:14-18`): the scrape * target is PROVIDER-constructed (the ChatGPT-UI endpoint for a given * prompt), not caller-supplied free-text, so there is no SSRF surface * here the way there is for `DamcrawlerCitationVerifier`'s * caller-supplied candidate urls. * - `ApiSpineEngineProvider` (`./api-spine-provider.ts:72-125`) — the * N-samples-per-prompt loop: sanitize -> extract -> verify -> emit one * raw `AiVisibilityRow` per real observation, never a pre-aggregate. * * This class is the FIRST live consumer of the `"ai-visibility-scrape"` * egress axis (previously axis-only, `../egress.ts:6-7`). The axis is * self-asserted as the FIRST statement inside `probe()` — the load-bearing * barrier per ADR-4 — so axis-off means zero sockets AND zero damcrawler * import AND zero `ScrapeThrottle.acquire` calls (sc-10-1, * byte-identical-when-off). * * When the axis is ON, each sample runs: * `throttle.acquire` -> `dam.scrape` -> `ContentSanitizer.clean` (BEFORE * the parser — the Sprint-9 F1 prompt-injection lesson: an unsanitized * scraped answer reaching the extractor/verifier/builder would re-open * that regression, arch:346 "critical") -> `EngineScrapeParser.parse` * (PURE) -> `MentionCitationExtractor.extract` -> `CitationVerifier.verify` * -> `ScrapeThrottle.recordProxyCost` (only after a row is produced) -> * push a row labeled `this.name` (`"chatgpt-ui"` (Sprint 10) or * `"perplexity-ui"` (Sprint 11) — distinct from every API-arm provider * label, sc-10-4/sc-11-1 unmixable-by-label). * * `estCostUsdPerPrompt = 0` — the scrape arm books ZERO USD to the * `SeoQuotaGovernor`; its real proxy cost lives entirely in the * `ScrapeThrottle`'s independent ledger (Sprint 9). Unlike * `ApiSpineEngineProvider.probe` (which THROWS when every attempted sample * fails, so its non-zero USD is never over-booked, `api-spine-provider.ts * :117-122`), this arm books $0 — a total failure has no over-book risk, so * it returns `[]` (abstain) instead of throwing (sc-10-3, "a scrape error * degrades to abstain (never throws)", read literally). */ import type { SeoEgressGuard } from "../egress.js"; import type { AiVisibilityRow } from "../data-source.js"; import type { AiVisibilityProvider } from "./ai-visibility-adapter.js"; import type { MentionCitationExtractor } from "./mention-citation-extractor.js"; import type { CitationVerifier } from "./citation-verifier.js"; import type { ScrapeThrottle } from "../scrape-throttle.js"; import type { EngineScrapeParser } from "./engine-scrape-parser-chatgpt.js"; /** The scrape arm's engine labels (net-new; `AiVisibilityRow.provider` is a plain string). Both `"chatgpt-ui"` (Sprint 10) and `"perplexity-ui"` (Sprint 11) are live — see `scrapeUrlFor` below. */ export type ScrapeEngine = "chatgpt-ui" | "perplexity-ui"; /** * NARROW view of the ONLY damcrawler surface this arm calls — mirrors * `DamcrawlerSearchModule` (`./damcrawler-serp-provider.ts:43-49`) and * `DamcrawlerVerifyModule` (`./citation-verifier.ts:62-69`). Defined LOCALLY * (never imported from the real dep) so tests never need the package. * `options` carries the operator-supplied auth session / proxy opaquely — * this class does no auth harvesting or proxy sourcing (nonGoals). */ export interface DamcrawlerScrapeModule { scrape(urls: string[], options: { formats?: string[]; proxy?: string; authSession?: unknown; }): Promise; error?: string; }>>; sanitize(raw: string, options?: { sourceUrl?: string; }): { content: string; hadThreats: boolean; }; } /** Loader seam — the default performs the lazy dynamic import; tests inject a FAKE module (or `undefined` to simulate the dep being absent). */ export type DamcrawlerScrapeLoader = () => Promise; /** * `AiVisibilityProvider` backed by a gated damcrawler UI scrape. * Guard-first, lazy-load, throttle-metered, sanitize-before-parse, * never-throw; books zero USD to the governor (proxy cost is tracked * independently by the injected `ScrapeThrottle`). */ export declare class ScrapeArmEngineProvider implements AiVisibilityProvider { private readonly egress; private readonly parser; private readonly extractor; private readonly verifier; private readonly throttle; private readonly samplesPerPrompt; private readonly authSession; private readonly proxyUsdPerScrape; private readonly proxy; private readonly load; readonly name: ScrapeEngine; readonly estCostUsdPerPrompt = 0; constructor(egress: SeoEgressGuard, engine: ScrapeEngine, parser: EngineScrapeParser, extractor: MentionCitationExtractor, verifier: CitationVerifier, throttle: ScrapeThrottle, samplesPerPrompt: number, authSession: unknown, proxyUsdPerScrape: number, proxy?: string | undefined, load?: DamcrawlerScrapeLoader); probe(target: string, prompts: string[], _locale?: string): Promise; } //# sourceMappingURL=scrape-arm-provider.d.ts.map