Capture hardening: deterministic env shim, extended lazy sweep, nav budget with bot-wall fast-fail

Salvaged and re-implemented from PR #15:
- Deterministic environment shim (seeded Math.random, pinned-but-advancing
  Date consistent across constructor/now/getTime/valueOf/toISOString) so
  time/random-dependent pages capture reproducibly; epoch and seed are
  recorded run metadata, performance.now stays real for motion sampling
- Post-snapshot lazy-asset discovery sweep for channels the walker misses:
  noscript contents (DOMParser), inline style url(), data-background(-image),
  img[loading=lazy] currentSrc
- Navigation gets a total 90s budget, an early bot-wall probe that aborts
  with a pollution-style error instead of capturing garbage (WALL_RE now
  shared with the validator gate), and one fresh-context retry scoped to
  the initial nav on retryable failure classes only

505 tests pass (29 new), typecheck clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samraaj Bath
2026-07-04 13:49:14 -07:00
co-authored by Claude Fable 5
parent 3faf1ef043
commit 7e8863fc7e
7 changed files with 781 additions and 54 deletions
+3 -1
View File
@@ -7,6 +7,7 @@ import type { AssetGraph } from "../infer/assets.js";
import type { FontGraph } from "../infer/fonts.js";
import type { Section } from "../infer/sections.js";
import type { CaptureResult } from "../capture/capture.js";
import { WALL_RE } from "../util/captureFailure.js";
export type GateResult = {
gate: string;
@@ -117,7 +118,8 @@ export function countVisibleInCaptureHiddenInClone(
// page: an egress/bot wall, a near-empty shell, or a cookie/consent modal that was
// never dismissed (the clone then reproduces the modal, so the perceptual gate is
// fooled too). This gate flags those captures so a "perfect" score can't hide them.
const WALL_RE = /blocked by egress|access denied|access to this page has been denied|are you a (human|robot)|verify you are human|enable javascript to|please enable javascript|checking your browser|just a moment|attention required|request blocked|why have i been blocked|captcha|cf-browser-verification|ddos protection by/i;
// WALL_RE lives in util/captureFailure.ts — shared with the capture-side fast-fail so
// the abort and the grade can never drift on what counts as a wall.
export function gatePollution(ir: IR, capture: CaptureResult, viewports: number[]): GateResult {
const issues: string[] = [];