## M0 CDP network spike Run against the Clark binary declared by `clark-browser` 0.2.1: ```sh cd ../clark-browser python3 -m clarkbrowser fetch cd ../lume-ctrl M0_CDP_PORT=40746 M0_HTTP_PORT=40745 \ CLARK_BINARY_PATH="$HOME/.clarkbrowser/chromium-148.0.7778.96/chrome" npm run spike npm test ``` The two explicit loopback ports make the recorded run directly comparable; omit them to select free ports automatically, or change them if already occupied. One command performs three independent startup/full-traffic samples by default. Override `M0_STARTUP_SAMPLES` only when gathering a larger baseline; values below two are rejected. The spike starts only loopback listeners and uses generated, one-day self-signed TLS material in a temporary directory. Chromium accepts that local certificate with `--ignore-certificate-errors`; no proxy, CA installation, traffic interception, VNC, or external fixture is involved. `artifacts/m0-events.sample.jsonl` is the sanitized event output and `artifacts/m0-report.json` is the run result/resource snapshot. ### Connection and schema Clark is launched directly with `--remote-debugging-address=127.0.0.1`. The runner waits on `/json/version`, discovers the startup page through `/json/list`, connects to its `webSocketDebuggerUrl`, and navigates to the already-listening loopback fixture before triggering test traffic. This avoids `clarkserve`'s current `0.0.0.0` default; a protected gateway and short-lived credentials remain required outside a local Spike. Every JSONL line has `schema_version`, `session_id`, `target_id`, and `kind`. HTTP records use `request_id`; WebSocket records use the CDP `requestId` as `connection_id`. Kind-specific fields are nested under `request`, `response`, `headers`, `body`, or `error`. Query strings are removed, sensitive headers are replaced by `[REDACTED]`, and WebSocket payloads are never persisted. In schema `1.0`, `websocket_frame.payload_bytes` is the payload's original byte count (base64-decoded for opcode 2) and drives the frame-size limit. Binary frames additionally expose `encoded_payload_bytes` for the UTF-8 byte length of CDP's base64 `payloadData` text. Response bodies are safe by default and never contain content. Their stable metadata is `{state,source,encoding,storage,reason,bytes}`: `source` is `network`, `memory_cache`, `disk_cache`, `prefetch_cache`, or `service_worker`; `encoding` is `utf8`, `base64`, or `unknown`; and `storage` remains `omitted`. Oversize and CDP-unavailable bodies keep `size_limit` and `unavailable` states. The local fixture fetches a cacheable response twice and an `application/octet-stream` response to assert cached and binary semantics. Session filtering happens before pagination and export serialization. The validation closes two distinct CDP targets bound to two generated session IDs, then asserts that queries, exports, and disconnect records contain only the requested session/target. ### Coverage and limits | Case | Clark 0.2.1 / Chromium 148.0.7778.96 | Evidence or limit | | --- | --- | --- | | HTTP request → response → finished | Pass | Same `request_id` asserted for `/ok`, `/large`, and `/secure`. | | Failed HTTP request | Pass | Unused loopback port produces `loadingFailed`; error text retained. | | HTTPS without MITM | Pass | Direct browser connection to local TLS fixture; no network intermediary. | | WebSocket lifecycle and frames | Pass | Created, handshake, sent, received, and closed share one `connection_id`; payload omitted. | | WebSocket pressure limits | Pass | Defaults: 1,024 bytes/frame, 16 events/second, and 16 emitted events/connection. Drops are aggregated into one payload-free record with `frame_size_limit`, `rate_limit`, or `event_limit` counts. | | `getResponseBody` success | Pass | Called after `loadingFinished`; fetched bytes are recorded but content is omitted by default. | | Cached and binary body | Pass | Repeated `force-cache` response records its cache source; octet-stream records `encoding=base64`; neither persists content. | | Sensitive response body | Pass | Random secret fixture is fetched; serialized JSONL is asserted not to contain its value. | | Body size limit | Pass | 2 KiB fixture is reported as `size_limit` and not persisted with the 1 KiB default. | | Body unavailable | Pass | Calling on the failed request returns the original CDP error as structured `cdp_error`. | | Target close | Pass | Target connection closes; browser process remains alive. | | CDP disconnect/reconnect | Pass | Browser-side target close triggers the real socket `close` callback; the browser stays alive and a new target accepts a verified CDP command. | | Cookie / Authorization / Set-Cookie | Pass | `Network.*ExtraInfo` values are redacted before JSONL serialization. | | Multi-target/session isolation | Pass | Two target/session pairs keep query, export, and real socket-close records isolated. | | Startup `Page.navigate` | Sampled | Three successful startup/full-traffic samples are required; exact `Render process gone.` retries are recorded and must stay at or below a 0.34 failure rate. Other discovery, WebSocket, timeout, and post-startup errors fail immediately. | Each full sample takes five `ps` samples 200 ms apart from the browser process tree and reports min/max/mean/p95. `resource_samples` retains the summary for every startup/full-traffic sample; `resources` remains the latest summary for existing consumers. The local guardrails are RSS max 1,500,000 KiB and at most 20 processes; exceeding either fails the run. These are single-instance development thresholds, not production capacity claims. Tune with `M0_RESOURCE_SAMPLES`, `M0_RESOURCE_SAMPLE_INTERVAL_MS`, `M0_MAX_RSS_KIB`, and `M0_MAX_PROCESS_COUNT` under the intended container limits. The runner restarts Clark at most three times per startup sample only from the local navigation/readiness block, and only after CDP reports the exact detached reason `Render process gone.`. Every failed attempt is retained with sample, attempt, error, and detached reason. A timeout without that reason and every discovery, connection, or post-startup error fail immediately with the original message. The default renderer failure-rate guardrail is 0.34 and can be tightened with `M0_MAX_RENDERER_FAILURE_RATE` after more samples.