The header-hook manager's core call: capture when any positive signal holds and none of the vetoes do (docs/05 §2). Vetoes are absolute and checked first — a monitored .zip on an excluded host is not captured. Decision table written first (tests/capture/rules.test.ts, 22 rows); every branch here exists to satisfy one. Covers the M1 DoD set: attachment, monitored extension, monitored MIME, size threshold, excluded host (exact + wildcard), HTML navigation, blob:/data: origin, bypass modifier, streaming media (HLS MIME and resourceType 'media'), a page-issued range request, plus non-GET, redirect status, sub-threshold, and large-but-renderable. Pure function of (candidate, rules); rules are the daemon's, mirrored via capture.getRules, so the decision never drifts from daemon policy. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_012Y9RU58hD1BuwP82DySUHk
Owner: lane EXT. See ../docs/agents/AGENT-EXT.md and ../docs/05-extension-spec.md. Firefox MV3, TypeScript. Zero download logic.
Layout
src/background/transport/ Transport interface + WebSocket and native-messaging impls,
runtime picker. See docs/adr/0003 for why there are two.
src/shared/protocol/ GENERATED from ../contracts — never hand-edit.
tests/ vitest; webextension-polyfill is mocked in tests/setup.ts.
Develop
npm ci
npm run typecheck # tsc --noEmit, strict
npm test # vitest run
npm run lint # web-ext lint (AMO rules) — needs manifest.json
Build against tools/mockd over the WebSocket transport; veloxd is not required.
Transport quick start
import { createTransport } from './src/background/transport/index.js';
const t = await createTransport(); // reads the Options override; default 'auto'
t.onStateChange((s) => renderDot(s));
const rules = await t.call('capture.getRules', {});
createTransport resolves with a live, self-reconnecting transport. When veloxd is not
up yet it still resolves (WebSocket, status disconnected, retrying) — callers render the
red dot. It rejects only when the user explicitly forced native messaging and that failed.
Capture code treats every call() rejection as fail-open.