GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • GenClass
  • Docs
  • Pricing
  • GitHub
  • npm
  • Discord
  • Sign in
  • Get started
  • Dashboard guide
  • The genclass skill
  • Open source
  • API Reference
  • Options Reference
  • Architecture

This view

  • Original page
  • New address

GeezerVison is showing genclass.dev — GenClass Interception Surface

GenClass Interception Surface

Every browser API GenClass wraps or observes, what each mode may change, what it never does, and how it restores the originals.

Everything @genclass/runtime wraps, replaces or listens to in a page, what each mode may change, what it never does, how every action can be undone, and how everything is put back. The source is the code; each row names the file and function. test/interception.test.ts checks this page against the code: it installs the runtime on a synthetic browser global, compares what changed with the inventory at the end of this page, checks that destroy() restores it, and counts the patch sites in src/ (a new addEventListener, defineProperty or global assignment fails the test until this page is updated).

Version. Written for @genclass/runtime 0.3.0-beta.5 as it is on GitHub main (released 2026-11-02). The copy in an npm package describes that package's version. As of 2026-11-02, npm i @genclass/runtime@beta installs 0.3.0-beta.5 (the 0.3.0 line: 0.3.0-beta.1 to 0.3.0-beta.5, released 2026-10-22 to 2026-11-02), and npm i @genclass/runtime installs 0.2.3 (latest, 2026-10-28), which has no measured visual checks, no protect(), no webhook sink and no interaction analytics. Items marked 0.3.0 are not in 0.2.x; the interaction analytics rows (src/analytics/) and the devtools overlay's theme: "dark" default are 0.3.0-beta.1, the genclass.analytics.<token> entry 0.3.0-beta.2.

The src/... links are relative to packages/runtime/ in the repository (GenClass on GitHub): the npm package ships the built dist/, not src/.

Related: THREAT-MODEL.md (what an attacker could do with this surface), SECURITY.md, TELEMETRY.md (what leaves the page), OPTIONS-SPEC.md (every option), API.md.

Contents

  • At a glance
  • Globals and prototypes it replaces
  • Event listeners it adds
  • Objects it instruments after handing them to the app
  • Automatic state discovery (React, Redux, Zustand)
  • Stores you register
  • Other side effects
  • The coding-agent link (development only)
  • What each mode may change
  • What it never does
  • Actions: preconditions and reversibility
  • Money and identity flows
  • Turning it off and restoring the page
  • Audit trail
  • Machine-checked inventory

At a glance

Mode Wraps and listens Holds (adds latency) Changes what the app sees
?genclass=off, enabled: false nothing never never
observe (default) everything below never never
guard (opt-in) everything below a salient request, response, failure or (with policy.holdWrites) store write, up to the hold budget (150 to 800 ms) only through guard-tier actions: discard, defer, coalesce, delay
heal (experimental) everything below as guard guard-tier plus block, serve_cached, retry, hedge, rollback, resync, custom actions
any mode + connectAgent (@genclass/runtime/agent, development hosts, imported by the app's dev build) also the delivery gate and the request ignore list (below) only with a schedule the developer or their agent set: a response waits for others, at most maxHoldMs (1.5 s) never what a response contains; only when it reaches the app

Every action needs the model's calibrated probability over the permitted actions to reach the tier threshold, a diagnosis other than expected, and must pass requests.protect, cross-origin, route scope, policy.allow/deny, action limits, the breaker and onBeforeAction (src/decide/policy.ts gate, src/runtime.ts RuntimeImpl.onDecision). The model never runs code: it picks a label from the list of actions the runtime offered for that subject, and a label that was not offered is ignored.

Observers are installed by RuntimeImpl.installObservers (src/runtime.ts); observe: { fetch: false, ... } leaves any of them out (timers defaults to on only when a document exists). Each installer is wrapped in try/catch: an installer that throws (a read-only global) is skipped, never fatal.

Globals and prototypes it replaces

Each wrapper calls the original with the same this and arguments and returns its result. After destroy() a wrapper is restored only if it is still the installed value; if another library wrapped it afterwards, GenClass's wrapper stays in that chain as a pure pass-through (a disabled flag), so neither library breaks.

Global How File, function What it reads What it may change (guard/heal only) After destroy()
window.fetch replaced by a wrapper src/observe/fetch.ts installFetch method, URL, header names, a hash of header values (tracing ids excluded), the request body (strings, form data, ≤ 64 KB of a Blob, BufferSource or of one clone of a Request stream; a Request whose content-length is larger is not cloned at all) to compute an identity; a clone of each response (≤ 256 KB, ≤ 1 s; never text/event-stream) hold before sending; coalesce, delay, block, serve_cached (request); hold a failure, retry, serve_cached (failure); hedge, serve_cached (stall); hold a response (delivery defer, discard) restored
XMLHttpRequest.prototype.open, send, abort, setRequestHeader replaced on the prototype src/observe/xhr.ts installXHR method, URL, header names and values (for the identity hash), the body (strings and BufferSource for the identity; Blobs are not read), the completed response's JSON hold before send; delay, block, serve_cached (request); hold the completion events (delivery). XHR failures are detection only (no retry) restored
XMLHttpRequest.prototype.addEventListener, removeEventListener replaced on the prototype (completion listeners readystatechange, progress, load, loadend go through a thin wrapper so a held delivery can queue them) same nothing the order and time at which completion listeners run while a delivery is held restored, unless a wrapped listener was added: then both stay as pass-throughs so removeEventListener still finds the wrapper
window.WebSocket replaced by a subclass of the native constructor src/observe/websocket.ts installWebSocket URL, message data (a text message is JSON-parsed and summarised only when something reads it: the delivery decision of a salient message, a situation, devtools; once, one parse for both), outgoing send summaries (likewise when read) hold an incoming message (and everything after it on the same socket, in order); never drops or changes one restored; sockets created before stay subclass instances that pass everything through
window.EventSource replaced by a subclass src/observe/eventsource.ts installEventSource URL, event data (parsed and summarised when read, as for WebSocket) hold an incoming event (order kept) restored, as above
window.setTimeout, window.setInterval replaced by wrappers that wrap the callback (a lazy op carries the causal context) src/observe/timers.ts installTimers the delay (for the op's label) nothing: the delay, this, arguments, return value and the timer id are the native ones restored
history.pushState, history.replaceState replaced on the history object; the original runs first src/observe/nav.ts installNav the new path, query and hash (redacted before the model) nothing restored
Storage.prototype.setItem, removeItem, clear replaced on the prototype; the original runs first src/observe/storage.ts installStorage the key name only (redacted when it names a secret); never the value nothing restored
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ installed when absent (a minimal DevTools hook); when React DevTools or React Refresh already installed one, its inject, onCommitFiberRoot and onCommitFiberUnmount are chained (theirs run first) src/discover/react.ts installReactDiscovery see state discovery nothing (observed only) chained functions restored; a hook GenClass installed stays (React keeps a reference to it) and is inert
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__, window.__REDUX_DEVTOOLS_EXTENSION__ replaced by shims that call the real Redux DevTools extension first when it is installed src/discover/redux.ts installReduxDiscovery see state discovery Redux stores created through them become full adapters (their writes can be held, dropped, rolled back in guard/heal) restored (deleted when they did not exist)
window.GenClass the script-tag API object src/cdn/global.ts install nothing nothing stays
PerformanceObserver (an instance, not a patch) observes longtask entries src/observe/perf.ts installPerf long task durations nothing disconnected
PerformanceObserver, MutationObserver (instances; measured visual checks only) layout-shift and resource entries (buffered); child list, text and the class, style, hidden, src, srcset, width, height, open attributes under documentElement src/visual/engine.ts Visual.install shift scores and their sources, resource URLs and responseStatus; which subtrees changed nothing disconnected
MutationObserver (an instance; interaction analytics only, InitOptions.analytics) child list, attributes and text under documentElement, for at most 1 s after a click on a link or button src/analytics/index.ts startAnalytics whether anything in the page changed (a dead click is one after which nothing did); changes inside [data-genclass-ignore] do not count nothing disconnected after 1 s, at the first change, and on stop() or destroy()
document.adoptedStyleSheets (measured visual checks, guard or heal with visual: { on: true, fix: true } only) one constructed stylesheet appended, one rule per fix ([data-genclass-fix~="f3"] { color: ... !important }) and that token in a data-genclass-fix attribute on the element src/visual/fix.ts Fixer.apply nothing a readable text colour for text measured below 1.5:1 rules and attributes removed

Not wrapped or observed: requestAnimationFrame, queueMicrotask, MessageChannel, postMessage, BroadcastChannel, IndexedDB, Cache Storage reads by the app, navigator.sendBeacon, Worker, Service Workers, WebRTC, WebTransport, document.cookie, form navigation (a native <form> submit is recorded as a user action only).

Event listeners it adds

All of them are passive observers: none calls preventDefault, stopPropagation or changes the event. Exception: the devtools overlay's Alt+Shift+G hotkey (only when you mount the overlay).

Target Events File, function Why Removed by destroy()
window (capture, passive) click, input, change, submit, keydown (Enter and Escape only), plus focusin, pointerdown, mousedown to find open shadow roots src/observe/dom-user.ts installDomUser user actions: the target's accessible name, the typed value (empty for password, card, one-time-code and secret-named fields: isSensitiveField), the option text yes (also the per-shadow-root change/submit listeners)
window error, unhandledrejection src/observe/errors.ts installErrors errors become error triggers (message, file name and line) yes
window popstate, hashchange src/observe/nav.ts installNav navigation (route scopes are recomputed) yes
window / document pagehide / visibilitychange src/runtime.ts constructor the session summary for your sinks; in observe (0.3.0), deciding the situations deferred while the tab was hidden yes
window / document pagehide / visibilitychange src/telemetry/client.ts TelemetryClient the final telemetry flush (only while telemetry is on) yes
window load (once) src/model/host.ts scheduleIdle start the model download at idle after the page loaded yes
document securitypolicyviolation (while the model loads) src/model/host.ts watchCsp name a CSP-blocked model download in one warning yes, when loading ends
document DOMContentLoaded (once) src/cdn/config.ts whenBody mount the devtools overlay when <body> exists fires once
the model Worker message, error, messageerror src/model/host.ts, src/cdn/global.ts blobModuleWorker the worker GenClass started terminated
inside the worker message, securitypolicyviolation src/model/worker.ts the worker's own scope terminated
the devtools overlay (only when mounted) keydown on document (capture: Alt+Shift+G toggles the overlay and calls preventDefault on that combination only), clicks and keys inside its own shadow root, matchMedia theme changes (only with theme: "auto", the default before 0.3.0-beta.1) src/devtools/index.ts, src/devtools/ui.ts the overlay on unmount
document (capture), window securitypolicyviolation (at most 50 kept), load (once) src/visual/start.ts startVisual measured visual checks (VISUAL.md), only when InitOptions.visual is on for this page: violations during page load cannot be queried later; the checks' chunk loads 1.5 s after load securitypolicyviolation yes; load fires once and is inert after destroy()
document, window, document.fonts (capture, passive; only once the visual chunk runs) document: error, load, securitypolicyviolation, click, pointerover, focusin, pointerdown, pointerup, pointercancel, keydown, visibilitychange, prerenderingchange; window: wheel, touchmove, scroll, hashchange, popstate, resize, pageshow; document.fonts: loadingdone src/visual/engine.ts Visual.install measured visual checks: failed assets, clicks swallowed by an invisible layer, hover and focus contrast, focus under a sticky header, scroll attempts on a locked page. Read only: never preventDefault, stopPropagation or a write yes
window (capture, passive), document (only with InitOptions.analytics or rt.analytics.start(), once the analytics chunk runs) window: click, pointerdown, pointermove, keydown, wheel, touchstart (capture), scroll, pagehide, pageshow; document: visibilitychange, prerenderingchange (once, on a prerendered page) src/analytics/index.ts startAnalytics interaction analytics (ANALYTICS.md): page views, engaged time, scroll depth, and clicks, rage clicks and dead clicks per element key, sent as interactions telemetry events. Trusted events only; never preventDefault, stopPropagation or a write; nothing reaches the event log or the model yes, and on rt.analytics.stop()

Objects it instruments after handing them to the app

Object What File Mode Effect
each Response the app receives from fetch own properties json, text, arrayBuffer, blob, formData, bytes and clone that call the native methods and then restore the request's causal context src/observe/fetch.ts instrumentResponse all same results, one extra microtask; res.json !== Response.prototype.json
a Response GenClass made (guard/heal actions only) a new Response with the header x-genclass: cached, coalesced or blocked (503, empty body) src/observe/cache.ts makeResponse, blockedResponse guard: coalesce; heal: block, serve_cached the app can tell it apart by that header
each XMLHttpRequest the app uses own accessor properties onreadystatechange, onprogress, onload, onloadend (the getter returns the wrapper around the app's function) src/observe/xhr.ts wrapHandlers all same calls, possibly queued while a delivery is held
an XMLHttpRequest GenClass answered (heal: block, serve_cached) own properties readyState, status, statusText, response, responseText, getResponseHeader, getAllResponseHeaders, then readystatechange, load, loadend are dispatched; all removed on the next open() src/observe/xhr.ts fake, unfake heal the response carries x-genclass
each WebSocket created while installed an own send that records the send (its summary computed when read), then calls the native send with the same data; GenClass's own listeners for message, close, error, open src/observe/websocket.ts, src/observe/messages.ts MessageGate all; holding only in guard/heal a held message is stopped (stopImmediatePropagation) and later re-dispatched as a copy (isTrusted false, same data, origin, lastEventId, ports), in order
each EventSource addEventListener (subclass method) makes sure GenClass sees custom event types first src/observe/eventsource.ts all; holding only in guard/heal as WebSocket

Automatic state discovery (React, Redux, Zustand)

Installed with autoState (on by default for @genclass/runtime/auto and the script tag; GenClass.init needs import "@genclass/runtime/discover" first), never under ?genclass=off. Code: src/discover/.

Source Hook point What GenClass does there Store kind May GenClass change it?
React ≥ 16.8 (all renderers, dev and prod builds) __REACT_DEVTOOLS_GLOBAL_HOOK__ (above) on each commit, walks only the fibers that re-rendered (≤ 1 ms, ≤ 20,000 fibers per commit) and records useState, useReducer, useSyncExternalStore and class-component state of named components (≤ 3 instances per component, ≤ 48 component stores, ≤ 16 hooks each). Functions, promises, elements, DOM nodes and class instances are skipped; a value equal to what a password, card or one-time-code input holds is redacted from then on observed No. Never held, dropped, reverted, rolled back or resynced (state/hub.ts StoreHub.observe only records; mutation discard/defer are not offered: situation/build.ts builtinUnavailable; rollback needs a writable store)
React renderers that inject after GenClass the renderer's hooks dispatcher (ReactSharedInternals.H in React 19, ReactCurrentDispatcher.current before) becomes an accessor that returns an object inheriting from React's dispatcher with own useState/useReducer whose setters are wrapped; class instances' updater is wrapped the wrapper calls the real setter unchanged and first notes which operation (request, timer, user action) called it none No. After destroy() the wrappers stay (React keeps the setters' identity) and pass through
Redux and Redux Toolkit stores created with the Redux DevTools compose or enhancer (RTK's default) __REDUX_DEVTOOLS_EXTENSION_COMPOSE__, __REDUX_DEVTOOLS_EXTENSION__() adds GenClass's Redux enhancer innermost (as the manual genclassEnhancer, src/adapters/redux.ts); the real extension stays outermost adapter (full) Yes, in guard/heal: like a registered store (a salient async dispatch may be held with policy.holdWrites, dropped, late-reverted within 800 ms, rolled back). Redux stores created before a runtime attached are observed only
Zustand devtools() middleware and other Redux DevTools connect() clients __REDUX_DEVTOOLS_EXTENSION__.connect records every state they send (functions left out), attributed to the operation that made the change observed No

test/invariants/discovery.test.ts checks the "No" rows with a hostile model in heal mode.

Stores you register

rt.atom, rt.guard, rt.adapter, useGenClassState (src/adapters/react.ts), the Redux enhancer (src/adapters/redux.ts: wraps the store's dispatch and previews each action through the reducer once) and the Zustand middleware (src/adapters/zustand.ts: replaces the store's set and api.setState). These are the only stores GenClass can write: guard may drop or defer a held write (policy.holdWrites, opt-in) or late-revert one within 800 ms; heal may roll back or resync. GenClass writes whole states back to Redux with the visible action @@genclass/REPLACE. Store options: hold: false keeps a store's writes from ever being held.

Other side effects

What Where When File
Network: the model card, tokenizer, calibration, meta and the model variant (about 10 MB) from model.baseUrl (default jsDelivr @genclass/runtime-model), and onnxruntime-web's wasm from model.ortWasmPaths with the page's fetch as it was when the module loaded (never through the wrapper) at idle after load, GenClass.init in a browser with a model; not on devices reporting 2 GB or less nor on Save-Data (model.loadIf default, 0.3.0; 0.2.x loads on every device, and on Save-Data only when first needed) src/model/loader.ts, src/model/backend.ts
Network: anonymous diagnostics to the GenClass collector (DEFAULT_TELEMETRY_ENDPOINT, a Cloudflare Worker): a summary about once a minute while decisions continue; every detection with its decision record, and every other non-routine decision record; routine decision records (top diagnosis expected, nothing acted) sampled 1 in 4 and capped at 200 per page load (0.3.0; 0.2.x sends every decision record) native fetch/sendBeacon captured at module load, text/plain, credentials: "omit", referrerPolicy: "no-referrer" GenClass.init in a browser unless turned off (TELEMETRY.md); never in createRuntime() src/telemetry/
Network (0.3.0): a webhook sink's batched POSTs of { schema: "genclass-sink/1", version, records } to the URL the app configured, only when the app adds webhook() from @genclass/runtime/sinks/webhook or names webhook:<url> in its config (init --sink webhook:<url>) with /auto and a named sink, first a dynamic import() of the sink resolver and then of the adapter, both lazy chunks of the app's own bundle; with the script tag, a dynamic import() of the adapter, dist/sinks/webhook.js, from where the script was loaded (the CDN, or data-base when self-hosted); a named console-json sink loads its adapter the same way and sends nothing. The POSTs use navigator.sendBeacon, else fetch with keepalive, both looked up at send time; headers force the fetch path; when the fetch path is used, list the URL in requests.ignore (the zero-code entries do this for webhook:<url>) with the zero-code entries, the imports when the first sink record arrives; then each round of sink records, and on pagehide or when the tab is hidden src/sinks/webhook.ts, src/cdn/sinks.ts namedSinks, src/cdn/sinks-load.ts
Cache Storage genclass-model cache (model.cacheName): model files, each checked against the card's size and sha256 model load src/model/loader.ts fetchFile
sessionStorage genclass:bucket (sample: act@<sample> or observe@<sample>, reused only under the same sample; never read or written at sample 0 or 1), the breaker's trip state sample < 1; breaker trips src/runtime.ts sampleBucket, src/decide/breaker.ts
localStorage / sessionStorage genclass:learn (only with learn.expectations: true (0.3.0) or learn.persist: learned flows, and operation shapes too with learn.persist: signatures, routes and field names with id-like parts normalised, counts, hashed action labels; ≤ learn.maxBytes), genclass:learn:probe (set and removed once at init when a store is wanted, to check storage works), genclass:learn:session in sessionStorage (the tab's session ordinal, with a localStorage store) only with learn.expectations: true or learn.persist (by default nothing is stored); with learn.expectations alone, not in jsdom / happy-dom; learn.persist: false turns it all off; the runtime's own writes are not recorded as storage events src/runtime.ts saveProfilesSoon, startSession, docs/runtime/EXPECTATIONS.md
localStorage genclass.analytics.<token>: the visit id (16 random hex characters), the time of the visit's last page view or event, and a visited-before flag; written with the storage observer muted (rt.selfStorage), so it never becomes a storage event interaction analytics on (InitOptions.analytics, rt.analytics.start()): each page view and each interactions event; removed by rt.analytics.stop() src/analytics/index.ts startAnalytics
localStorage (read only) genclass (kill switch; read only when the URL has no ?genclass= value, which takes precedence), genclass.telemetry (opt-out) init src/index.ts killSwitch, src/telemetry/config.ts
A module Worker (genclass-model) running onnxruntime-web; inline on the main thread only if the worker cannot start (model.inlineFallback, default true; false (0.3.0; 0.2.x ignores it): no model then, status skipped / worker-unavailable) model load src/model/host.ts
Memory ≤ 64 response bodies of ≤ 256 KB (last good GET per identity), ≤ 4 MB of recent responses for coalescing, a 500-event history, 200 decisions, 1,000 audit entries always src/observe/cache.ts
Console one line per detection or intervention (report), the telemetry notice, warnings report: "console" (default) src/decide/report.ts
Network: the measured visual checks' chunk (dist/visual/index.js, from the same place as the runtime) a dynamic import() 1.5 s after load when InitOptions.visual is on for the page (development hosts by default), or when the devtools overlay mounts or rt.checkNow() runs src/visual/start.ts
Network: the interaction analytics chunk (dist/analytics/index.js, from the same place as the runtime), then interactions events on the telemetry batches a dynamic import() only when the app turned analytics on (InitOptions.analytics, rt.analytics.start()) and telemetry is on with the app's token or a custom endpoint src/analytics/start.ts
Console [GenClass] Check (measured): ..., one collapsed group per finding with its measured values (at most 10 a pass, then one line) measured visual checks on src/visual/bridge.ts
Console [GenClass] Interaction analytics are on ... once per page; [GenClass] analytics: true needs the app's token; nothing is recorded. analytics on; analytics: true without a token src/analytics/index.ts, src/analytics/start.ts

The coding-agent link (development only)

connectAgent(rt) from @genclass/runtime/agent (docs/runtime/AGENT.md) is not part of GenClass.init(), /auto or the script tag: an app imports it in its development build. It returns an inert connection on any page not served from this machine (localhost, 127.x, *.localhost; anyHost: true widens it) and with a non-loopback bridge URL. Connected, it:

What Where When File
Wraps the runtime's delivery gate (RuntimeImpl.runDelivery) the instance property; disconnect() (and rt.destroy()) restores it and delivers anything held always while connected; holds only fetch/XHR responses and only with a schedule (fifo, seeded, given): each at most its delay plus maxHoldMs; without a schedule a page load's first responses wait for the bridge's hello (at most helloTimeoutMs, 600 ms; hold: false turns every hold off); failures, aborts, WebSocket/EventSource messages, protected requests and routes in mode "off" are never held src/agent/index.ts, src/agent/schedule.ts
Wraps the request ignore list (RuntimeImpl.mIgnore) so the fetch observer passes the link's own requests to the bridge straight through (no op, no event) while connected src/agent/index.ts
Wraps window.setTimeout and window.clearTimeout (over the runtime's own wrap) counts pending app timers of at most 1 s (debounces) so a page is not "quiet" before its debounced request starts; every call goes to the original with the same arguments; intervals and longer timers pass untouched while connected; disconnect() restores both if nothing wrapped them since src/agent/index.ts
Listens to error, unhandledrejection (for stacks) and pagehide (final flush) on the page's global non-capturing, passive reads while connected src/agent/index.ts
Reads the page's visible text (document.body.innerText) and form values (not password, card, one-time-code or secret-named fields) at quiet points (no event for 400 ms) while connected, dom: true (default) src/agent/index.ts snapshot
Network: hello and batched events to the local bridge (http://127.0.0.1:47357) the page's fetch (passed through by the ignore list) and sendBeacon on exit; text/plain, credentials: "omit", referrerPolicy: "no-referrer"; loopback URLs only while connected src/agent/link.ts
Global __GENCLASS_AGENT__ (the connection, for tests); reads __GENCLASS_SCHEDULE__ and __GENCLASS_AGENT_URL__ while connected src/agent/index.ts

It never writes to the event log and never feeds a situation, a decision or the model.

What each mode may change

observe (the default) never holds, delays, drops, retries, replays or answers anything, and never writes app state. In code: permittedActions is empty in observe (src/decide/policy.ts modeAllows), so trigger() never waits; deliveryHoldable is false, so a response is released synchronously before any body read (runDelivery); requestHoldable is false, so a request whose body GenClass reads for its identity is sent first (src/observe/fetch.ts runRequest); store holds need mode !== "observe" (hub.hooks.mayHold). What observe does cost: the wrappers above, a few extra microtask hops, response clones, CPU for situations and the model (in its worker), the model download and, from GenClass.init, telemetry. test/invariants/observe.test.ts compares everything the app observes, with virtual timestamps, against a run without GenClass while a hostile model answers every situation.

guard (opt-in) adds holds and the guard-tier actions; heal (experimental) adds the heal tier. A hold never exceeds the hold budget (policy.holdBudgetMs, "auto" = 1.5 × the model's median latency clamped to 150 to 800 ms), including the time spent waiting for a body, deferred re-decisions and onBeforeAction (test/invariants/network.test.ts, test/invariants/state.test.ts "the hold budget caps ..."). When the model is not ready, slow, erroring or over its rate limit, the subject proceeds unchanged (fail open).

Measured visual checks (VISUAL.md) only read in every mode. Their runtime fixes need visual: { on: true, fix: true } and guard or heal mode, and only ever do two things: add an owned stylesheet rule that sets a readable colour on one element (text measured below 1.5:1), or remove one leaked inline pointer-events: none or overflow: hidden declaration on html or body after recording it. Each is re-measured 50 ms later and undone if it did not help, undone by finding.fix.undo(), and undone automatically on setMode("observe"), the breaker, pause() (within 2 s), disable() and destroy() (src/visual/fix.ts).

Every term can only lower the mode: routes rules, sample, the breaker (after 2 undos or 3 errors following actions, the session drops to observe), a hidden tab (nothing new is held; background decisions are skipped in guard and heal, and deferred until the tab is visible in observe, where they can only detect) and ?genclass-mode (and ?genclass-aggr, ?genclass-sample=0), on every host. Since 0.2.2 no URL or localStorage switch can raise the mode on a production host, and since 0.2.3 and 0.3.0-beta.2 debug: true does not change that: ?genclass=guard or heal raises only on a development host, http(s) on a loopback name with an explicit port or *.test (src/devhost.ts; never Capacitor, Tauri, Electron, intranet or private-address hosts; see THREAT-MODEL.md).

What it never does

Never How it is ensured Test
changes a request's URL, method, headers or body the native fetch/send gets the app's own input/init/body; a replay (retry, hedge) sends the same init, or a Request rebuilt from the original with the ≤ 64 KB of body bytes read for its identity (larger Request bodies are not replayable) test/fetch.test.ts, test/invariants/network.test.ts
reads or writes cookies, or changes credentials document.cookie is never accessed; init is passed through static: no cookie access in src/ outside redaction name lists
sends a non-idempotent request twice without an idempotency key retry and hedge are only offered for GET/HEAD/OPTIONS/PUT/DELETE/TRACE or with an Idempotency-Key-style header (policy.idempotencyHeaders) or opt-in body field (policy.idempotencyBodyFields): situation/build.ts repeatUnsafe; hedge is GET only test/invariants/network.test.ts "never sent twice"
serves a cached response to anything but an identical GET serve_cached needs method === "GET" and a cached body for the same identity (method, URL, headers, body) test/review-fetch.test.ts, test/fetch.test.ts
acts on a protected, cross-origin or off/observe-scoped subject gate steps 1 to 3 (blockOf → reason protected, cross-origin, scope), checked before thresholds, allow, eager and shadow; onBeforeAction is not even asked; a protected request is never held and never waits for its body; its response's causal chain is protected too test/invariants/network.test.ts, test/presets.test.ts
runs an action the runtime did not offer top must be one of built.actions; the gate only considers permitted, offered actions test/invariants/network.test.ts "out-of-vocabulary"
writes discovered React or Zustand state observed stores are only recorded (StoreHub.observe) test/invariants/discovery.test.ts
reorders writes to a store, or messages on a socket held writes wait in the store's queue (StoreHub.drain applies in proposal order); held messages queue behind the head (MessageGate) test/no-reorder.test.ts, test/invariants/network.test.ts "sync loop"
records password, card or one-time-code input values isSensitiveField → empty value; the default redactor runs first and a custom redact can only redact more test/redaction-v2.test.ts, test/review-redaction.test.ts
throws into the app installers, hooks, listeners, sinks and plugins run in try/catch; a failed action runs the passive action throughout
keeps acting after disable() holds are released unchanged, observers uninstalled, the model worker terminated test/invariants/state.test.ts "disable"

Actions: preconditions and reversibility

Holding (waiting for the decision) is not an action: it only adds latency, within the hold budget, and the subject then proceeds or the chosen action runs. Every action is recorded (rt.interventions(), rt.explain(id), the console, sinks, rt.audit()) with one sentence saying exactly what changed. Undo: record.undo(), the devtools overlay, or rt.disable({ undo: true }) (actions of the last 60 s, newest first). Two undos within 10 minutes trip the breaker.

Trigger Action Tier What it does Preconditions (besides the gate) Undo
mutation discard guard drops a held store write; decided after the write applied (hold budget expired), reverts exactly that write registered store (not observed-only); held: policy.holdWrites; late revert ≤ 800 ms after it applied and nothing written since depends on it (StoreHub.revertable) yes: the write is committed / re-applied
mutation defer guard holds the write until related in-flight operations finish, then decides again held write, at most 2 defers, within what is left of the hold budget not needed: the write applies or is decided again
request coalesce guard does not send; answers with the response of the identical request in flight or finished ≤ 2 s ago (x-genclass: coalesced) fetch; same identity (method, URL, headers, body); a shareable buffered response (≤ 256 KB) no: the request was not sent. Any method, POST included (double submits): protect or deny it where repeats are deliberate
request delay guard sends after 250 ms × 2^(recent failure streak), at most 8 s fetch, XHR no (latency only)
request block heal does not send; answers 503 with x-genclass: blocked fetch, XHR no
request serve_cached heal does not send; answers with the last good response for this identity (x-genclass: cached) GET; a cached body (≤ 256 KB, this page's memory) no
delivery discard guard delivers the response, then for 10 s drops the writes its causal chain makes over newer data the response would overwrite newer data; at least one target field in a registered store yes: the dropped values are restored
delivery defer guard holds the response until related in-flight operations finish, then decides again related work in flight; at most 2 defers; within the hold budget not needed: the response is delivered or decided again
failure retry heal sends the request again after 200 ms × 2^(attempt − 1) (≤ 5 s); the app gets that attempt's result fetch; replayable body; fewer than 4 attempts; idempotent method or idempotency key no
failure serve_cached heal answers the failed request with the last good response fetch; GET; cached body no
stall hedge heal sends a second identical request; the first good answer reaches the app fetch; GET; replayable no
stall serve_cached heal answers with the cached response; the original continues in the background fetch; GET; cached body no
inconsistency rollback heal restores the involved writable stores to the last consistent snapshot a snapshot exists; an involved store is writable yes: the replaced values are restored and the violation is muted
inconsistency, transition resync heal calls the store's resync handler (your code) a resync handler your handler
transition, error rollback heal restores only the fields the operation's own chain wrote (and nobody overwrote since) such fields exist in writable stores yes
any custom (rt.action, plugins) heal unless declared guard your code; ctx.builtin(name) runs a built-in under the same gate on triggers; applicable() ctx.onUndo(fn)

Money and identity flows

Automatic intervention in payment, checkout and sign-in flows is not recommended. Keep them observe-only with requests.protect, which is checked before everything else and covers the request, its response and everything its response callbacks cause:

import { GenClass, protectPreset } from "@genclass/runtime";
GenClass.init({ mode: "guard", requests: { protect: [...protectPreset("payments", "auth"), "/api/cart"] } });
// JSON configs (window.GENCLASS_CONFIG, the options file `init` writes, a meta tag):
window.GENCLASS_CONFIG = { requests: { protect: ["preset:payments", "preset:auth"] } };

npx @genclass/runtime@beta init suggests the second form when the project depends on a payment SDK (Stripe, PayPal, Braintree, Adyen, Square, Paddle, ...) or has source files named like a checkout or payments page. The presets (src/presets.ts) match URL words such as checkout, payment(s), payment_intents, billing, invoice, charge, refund, subscription, order, transaction, transfer, payout, wallet and provider names (Stripe, PayPal, Braintree, Adyen, Klarna, Square); auth matches login, logout, sign-in, sign-up, oauth, token, session, sso, saml, oidc, mfa, otp, password, verify. They over-match on purpose (protection only removes interventions). Cross-origin requests (a payment provider's own API) are never acted on in any case. Alternatively put the routes under a routes rule with mode: "observe".

Turning it off and restoring the page

How Effect Code
?genclass=off or localStorage.genclass = "off" nothing is installed: no observer, no discovery hooks, no model, no telemetry src/index.ts initUnsafe, src/cdn/config.ts isKilled
enabled: false installs nothing RuntimeImpl constructor
enabled predicate / source turning false pass-through: held subjects released, nothing decided followEnabled
rt.pause() nothing is held or acted on until rt.resume()
rt.disable() releases every held subject unchanged, then destroy() disable
rt.disable({ undo: true }) also undoes the actions of the last 60 s, newest first (not counted by the breaker) disable
rt.destroy() / GenClass.destroy() telemetry's final flush, every uninstaller in reverse order (the "After destroy()" column above), plugins' cleanups, store subscriptions, the model worker destroy

Per-object instrumentation already handed to the app (a Response's methods, an XHR's handler accessors, sockets created while installed) stays attached and passes through.

Audit trail

rt.audit(n?) returns JSON-serialisable entries for every decision, action, undo, breaker trip and reset, and control change (setMode, setAggressiveness, pause, resume, enabled, disable), each with a timestamp, sequence number, session id, the effective and requested mode, the aggressiveness profile, the gate kind, thresholds and their source, the model's probabilities, the action proposed and the one that ran with the reason, the hold and the model (name, version, variant, device and the sha256 its file was verified against). InitOptions.audit sets the in-memory size (default 1,000) and a sink for your own logging. Query values in free text are replaced with "…". The trail is built from values the runtime already has: it never feeds a decision, never changes what the model reads and is never sent anywhere by GenClass (src/decide/audit.ts; API: API.md).

Machine-checked inventory

test/interception.test.ts parses this block. patch lines name what a runtime with every observer and autoState changes on a browser global, and whether destroy() restores it or leaves it in place (inert). listen lines are the listeners it adds to window and document (all removed by destroy()). sites lines count the patch sites per file in src/ (addEventListener( calls, Object.defineProperty(, Reflect.set/defineProperty, assignments to global, prototype and hook objects): a change in a count fails the test until the rows above and this block are updated. src/protect.ts counts two Object.defineProperty( calls that are not interceptions: protect() sets name and length on the wrapper function it returns (its own object, never a page global; nothing to restore).

patch window.fetch restored
patch window.setTimeout restored
patch window.setInterval restored
patch window.WebSocket restored
patch window.EventSource restored
patch window.__REACT_DEVTOOLS_GLOBAL_HOOK__ stays
patch window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ restored
patch window.__REDUX_DEVTOOLS_EXTENSION__ restored
patch XMLHttpRequest.prototype.open restored
patch XMLHttpRequest.prototype.send restored
patch XMLHttpRequest.prototype.abort restored
patch XMLHttpRequest.prototype.setRequestHeader restored
patch XMLHttpRequest.prototype.addEventListener restored
patch XMLHttpRequest.prototype.removeEventListener restored
patch history.pushState restored
patch history.replaceState restored
patch Storage.prototype.setItem restored
patch Storage.prototype.removeItem restored
patch Storage.prototype.clear restored
observe PerformanceObserver longtask
listen window click
listen window input
listen window change
listen window submit
listen window keydown
listen window focusin
listen window pointerdown
listen window mousedown
listen window error
listen window unhandledrejection
listen window popstate
listen window hashchange
listen window pagehide
listen document visibilitychange
sites src/adapters/zustand.ts 1
sites src/agent/index.ts 4
sites src/analytics/index.ts 2
sites src/cdn/config.ts 1
sites src/cdn/global.ts 3
sites src/devtools/index.ts 7
sites src/devtools/ui.ts 1
sites src/discover/react.ts 7
sites src/discover/redux.ts 2
sites src/model/backend.ts 2
sites src/model/host.ts 5
sites src/model/worker.ts 2
sites src/observe/cache.ts 1
sites src/observe/dom-user.ts 3
sites src/observe/errors.ts 2
sites src/observe/eventsource.ts 4
sites src/observe/fetch.ts 4
sites src/observe/nav.ts 6
sites src/observe/storage.ts 6
sites src/observe/timers.ts 4
sites src/observe/websocket.ts 4
sites src/observe/xhr.ts 15
sites src/protect.ts 2
sites src/runtime.ts 1
sites src/telemetry/client.ts 2
sites src/visual/engine.ts 3
sites src/visual/start.ts 1

Rendered from packages/runtime/INTERCEPTION.md in the GenClass repository.

You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.