diff --git a/docs/m0-spike.md b/docs/m0-spike.md index b593cb4..10a3c34 100644 --- a/docs/m0-spike.md +++ b/docs/m0-spike.md @@ -19,7 +19,7 @@ The spike starts only loopback listeners and uses generated, one-day self-signed 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` keeps its original meaning: the UTF-8 byte length of CDP `payloadData` (base64 text for opcode 2). Binary frames additionally expose `decoded_payload_bytes` for the decoded payload size, which is also used by the frame-size limit. +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. diff --git a/scripts/m0-spike.mjs b/scripts/m0-spike.mjs index aa4f23b..bad9499 100644 --- a/scripts/m0-spike.mjs +++ b/scripts/m0-spike.mjs @@ -601,10 +601,10 @@ export function eventCollector(sessionId, targetId, { case "Network.webSocketFrameReceived": { const direction = method.endsWith("Sent") ? "sent" : "received"; mark(`ws_${direction}`); - const payloadBytes = Buffer.byteLength(params.response.payloadData); - const decodedPayloadBytes = params.response.opcode === 2 + const encodedPayloadBytes = Buffer.byteLength(params.response.payloadData); + const payloadBytes = params.response.opcode === 2 ? Buffer.from(params.response.payloadData, "base64").length - : payloadBytes; + : encodedPayloadBytes; let state = webSocketLimits.get(requestId); if (!state) { state = { window: null, windowEvents: 0, emitted: 0 }; @@ -617,11 +617,11 @@ export function eventCollector(sessionId, targetId, { } state.windowEvents++; let dropReason; - if (decodedPayloadBytes > maxWebSocketFrameBytes) dropReason = "frame_size_limit"; + if (payloadBytes > maxWebSocketFrameBytes) dropReason = "frame_size_limit"; else if (state.windowEvents > maxWebSocketEventsPerSecond) dropReason = "rate_limit"; else if (state.emitted >= maxWebSocketEventsPerConnection) dropReason = "event_limit"; if (dropReason) { - dropWebSocketFrame(requestId, dropReason, decodedPayloadBytes); + dropWebSocketFrame(requestId, dropReason, payloadBytes); break; } state.emitted++; @@ -630,7 +630,7 @@ export function eventCollector(sessionId, targetId, { direction, opcode: params.response.opcode, payload_bytes: payloadBytes, - ...(params.response.opcode === 2 && { decoded_payload_bytes: decodedPayloadBytes }), + ...(params.response.opcode === 2 && { encoded_payload_bytes: encodedPayloadBytes }), payload: { state: "omitted", reason: "frame_policy" }, }); break; diff --git a/test/m0-spike.test.mjs b/test/m0-spike.test.mjs index c20fedd..ab5e83b 100644 --- a/test/m0-spike.test.mjs +++ b/test/m0-spike.test.mjs @@ -76,8 +76,8 @@ test("WebSocket limits aggregate drops without payloads", () => { connection_id: "ws-binary", direction: "received", opcode: 2, - payload_bytes: 4, - decoded_payload_bytes: 2, + payload_bytes: 2, + encoded_payload_bytes: 4, payload: { state: "omitted", reason: "frame_policy" }, }); });