# Opt-in diagnostic harvester experiment

Implementation: [#107](https://github.com/CosticaPuntaru/SnagBadger/issues/107), supporting the human-led [#82 experiment](https://github.com/CosticaPuntaru/SnagBadger/issues/82). Human usefulness/design acceptance remains pending. This slice implements diagnostics only; capture is #83 and combined launcher/report flow is #84.

## Run and compose

Run `pnpm dev:examples` and open `/harvesters/` (React) or `/harvesters/plain.html` (plain host). The local fixture provides actual HTTP and a bounded text WebSocket echo without external services. Both studies show inactive counts, host results, diagnostics, failures, bounded-buffer overflow, custom cart context, cleanup and repeat sessions. Collection/content/pre-recording checkboxes start off; stopping resets pre-recording. These controls configure the next study session, not the final reporter settings UI.

`@snagbadger/harvesters` exports four inert factories. Their implementations satisfy the accepted consumer-owned `Harvester` contract from `@snagbadger/reporting/session`. Importing, factory construction and registration do not wrap host functions, attach listeners, or read app state. Runtime `start` is the explicit collection boundary; inactive installation is not instrumentation. The package loads no React or CSS and core reporting imports no built-ins.

```ts
const http = createHttpHarvester({
  host: window,
  maxEntries: 100,
  maxContentBytes: 1024,
  filter: (url) => !url.includes('/account'),
  sanitize: (text) => text.replaceAll(companySecret, '[redacted]'),
  content: { headers: false, bodies: false },
});
const app = createAppHarvester({ read: () => ({ cartCount: cart.count }) });
```

Register selected factories in ordinary configuration objects, and compose another registration with the same ID to completely replace it. The study replaces the app registration with an approved snapshot of a real mutable cart store; traffic updates completed-order state. The runtime retains successful output beside an intentionally failing adapter. There is no central registry or mandatory wrapper type.

Developer options are final bounds. Context settings `headers`, `bodies`, and `messages` can narrow an enabled option to false; a true reporter value cannot enable content forbidden by factory options. App `read` is an explicit developer-approved context source, not automatic traversal of React, Redux, Zustand or storage. Use a redactor for domain secrets: built-in sanitization cannot recognize arbitrary sensitive application values.

## Observations and privacy

| Harvester  | Observed                                                                                                     | Intentionally unavailable                                                              |
| ---------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| HTTP fetch | Invocation URL/method; optional supplied headers/text request body                                           | Promise settlement, response status/headers/body; body embedded in an existing Request |
| HTTP XHR   | Requests opened during opt-in, completion status; optional headers/text request and text response            | Binary responses, earlier requests, non-text request bodies                            |
| WebSocket  | New sockets during opt-in, open/error/close and sent/received text-or-binary metadata; optional text content | Existing sockets and all unobserved earlier messages; binary content                   |
| Browser    | Sanitized URL, language, viewport, errors, rejection occurrence, popstate/hashchange                         | Cookies, storage, DOM contents, arbitrary rejection values; pushState interception     |
| App        | Developer-approved state snapshot at collection                                                              | Automatic library/store discovery or earlier state history                             |

Fetch returns the exact original promise without attaching settlement handlers. A handler would suppress the host's original `unhandledrejection`, even if the same promise were returned. Real-browser controls compare ignored and explicitly handled failed fetches with inactive/active collection and check original promise identity in the event. Response diagnostics are explicitly unavailable rather than implying successful or failed settlement. Fetch does not clone/tee or consume host streams.

URL credentials, query strings and fragments are removed before retention. Only HTTP(S) and WS(S) URLs retain path metadata; other schemes retain the scheme alone, preventing embedded data payloads and local identifiers from entering metadata even when content is enabled. Common authentication/cookie/API-key header values are redacted before retention, even with content enabled. Additional sanitization runs before storing text. Exceptions omit an observation and increment its diagnostic `failures` counter; runtime activation/collection/disposal failures separately appear in `SessionResult.failures`. Raw host results remain intact and separate from sanitized evidence. Non-text bodies/messages are omitted without reading streams/files/binary content. Browser error messages and approved app values need developer redaction if they can contain domain secrets.

Bounds: 1–1000 retained entries (default 100); 1–65536 UTF-8 bytes per string (default 1024), preserving a valid character prefix. Headers also have a 32-field and aggregate content budget. Oversized input/sanitizer output above four times the byte budget is omitted before encoding. App traversal is limited to depth six, 128 nodes and 32 fields/items per container before serialization; over-limit content is explicitly omitted. Retained history evicts oldest entries and counts dropped entries. XHR in-flight listeners and tracked new sockets are capped at the entry limit; traffic beyond the instrumentation cap remains host-owned and unobserved. There is no unbounded queue of fetch settlement work.

Filters, sanitizers and app callbacks are developer code and must cooperate: bounds cannot stop a callback that blocks, allocates excessively or deliberately exposes secrets. Relative URL metadata retains the path rather than inventing an origin. Paths/error text can contain sensitive data and need developer redaction. This experiment does not promise complete traffic coverage, recover unobserved history or provide a browser-support commitment.

## Lifecycle and preservation

Wrappers forward native calls/arguments and preserve native return values and thrown errors. XHR uses original prototype methods; WebSocket preserves constructor static properties, subclass construction and native instance identity. Diagnostic listeners never prevent default, stop propagation, abort requests or close host sockets. Finish/cancel/dispose removes owned listeners and restores wrappers only if still owned, preserving newer host replacements. Socket instance send descriptors are restored. Borrowed send methods forward to the native receiver first and record only against that actual receiver while it is tracked by this harvester; excluded, pre-opt-in and no-longer-tracked receivers remain unobserved. Already-cancelled starts acquire nothing. A duplicate native XHR send error does not lose ownership of the first listener. Disposal clears retained state and a new session acquires fresh resources.

Pre-recording uses the accepted diagnostic-only runtime phase and explicit developer permission; it never starts on import/construction. Stop ends collection and resets the opt-in in both studies. Carrying history across navigation/reloads or into another session is not implemented here; #84 owns integration with the reporter flow. Concurrent independent runtimes instrumenting the same host are not a supported composition; select one runtime and replace registrations through configuration.

## Checks and measured cost

Direct tests cover inactivity, exact promises/responses/rejections, no attached promise handlers, XHR results/duplicate sends/reopening into excluded traffic, socket identity/history limitations, filters/redactors/UTF-8 bounds, app snapshot/traversal/failures, reentrant cancellation during developer callbacks, cleanup and custom replacement. The bounded header accumulator owns one retention policy for fetch/XHR. Both studies consume the supported built package export. Built-consumer checks evaluate its inactive entry without browser/Node globals, compile its declarations with browser types, verify selected HTTP versus unselected socket diagnostics against a positive control, and reject eager browser activation. Static browser scenarios cover both hosts on desktop/mobile, including native XHR reuse/filtering, metadata-only/content-enabled collection, default-off controls, pre-record reset, overflow, failure isolation, repeat sessions and keyboard operation. Run `pnpm test:unit integrations/harvesters/tests` and `pnpm build`, then `pnpm test:pr-preview harvesters`; the latter is also included in the full gate.

A local Node 24 fake-resolved-fetch observation on 2026-10-12 measured median 13.40 ms inactive versus 29.11 ms active over 5000 calls (three samples after warmup), approximately 3.14 microseconds added per call. Both paths included the same promise-identity assertion; the active run retained 100 entries and dropped 19900. This measures synchronous metadata observation in a synthetic host, not browser/network latency, total CPU/memory overhead, or an accepted performance budget. `overhead.test.ts` records fresh samples; machine/JIT/concurrency variation is expected. The fixture performs real host traffic separately and does not claim zero cost.

T3 source-bound observed review and inline GitHub-hosted screenshots are required before review readiness. Check the actual live server identity and HTML source meta tag before observations, and renew evidence after source changes. Record environment failures separately from code failures on the implementation issue. Human #82 feedback remains separate from all technical evidence.
