Experiments: 12 October 2026. Article prepared 14 October 2026 with AI assistance; reader-package revision 17 October 2026; published 21 October 2026. No experiment was rerun for publication.

Our mock service sent HTTP 200 headers immediately, then delayed its body for 900 milliseconds. A client with a deadline covering the body recorded a timeout after 304.246 milliseconds. A deliberately faulty client cleared its deadline on receiving headers and completed after 913.246 milliseconds.

A dashboard containing only the HTTP status would put both requests in the same bucket. An operational log needs the application outcome and failure stage as well. It also needs a collection policy that does not copy every prompt and response into storage merely to distinguish those outcomes.

The experiment and its boundaries

We built a loopback HTTP service and a small educational logging adapter on Windows x64, Node 24.13.0 and Undici 7.18.2. The original C experiment made eight logged requests plus one faulty-timeout control request. A separate D follow-up made three requests. There were zero real model calls, no vendor SDK and no user data.

The eight C records and three D records belong to different runs and schema versions. Do not describe the total as eleven model evaluations or combine the canary counts into a general privacy score. The C record guide and D follow-up guide identify each input, prediction and output.

Four different meanings of HTTP 200

The original C log distinguishes these outcomes:

CaseHTTP statusApplication outcomeFailure stage
Valid response200successnone
Delayed body200timeoutresponse_body
Malformed JSON200invalid_jsondecode
Wrong response shape200invalid_schemavalidate
Rate limit429rate_limitedhttp_status
Server error500upstream_errorhttp_status

The remaining C cases were a successful alias response without usage and a successful response with hostile metadata. In this adapter, success means that an output string passed the toy response-shape check. It does not mean that a model answered correctly, that the metadata is all valid, or that a model ran at all.

The exact operational records retain the case identifiers. Schema validation and quality evaluation answer different questions; keep them separate in the data model and in incident reports.

Keep the deadline active while reading the body

The faulty control took about 2.510 milliseconds to receive headers, followed by approximately 910.736 milliseconds awaiting the body. Its total was 913.246 milliseconds. That total is not a body-only timing. A separate three-second guard kept the intentionally broken comparison bounded.

The correct-deadline case retained HTTP 200 alongside timeout and response_body. Its paired server record says the response body did not finish and the connection closed before the end. The server's prepared payload is not proof that the client received it. These are individual observations, not a latency benchmark or recommended timeout value.

MDN's Fetch guide distinguishes receiving the response from asynchronously consuming its body. Our experiment tests that distinction in this Node environment; it does not establish every provider SDK's streaming or cancellation behavior.

For your own adapter, test a response that sends headers and then stalls. Retain the intended deadline, total elapsed time, status if available and the failed stage. Test cancellation through the body-read path rather than treating arrival of headers as completion. MDN's AbortController reference also describes cancellation of body consumption; check the behavior of your actual client.

Decide what enters the log before writing it

The restricted logger constructs a fixed set of fields. It does not copy a response object and try to clean it afterward. Prompt text, output text, arbitrary error bodies and exception messages are never assigned to the ordinary event. Model names and finish reasons come from allowed vocabularies; unexpected metadata is omitted with the field names recorded.

Seven synthetic canaries were absent from the eight C operational records. The preserved mock observations confirm that the corresponding inputs or prepared payloads actually existed. An offline over-collecting control retained four synthetic data classes, demonstrating the contrasting collection pattern without making another HTTP request.

The D follow-up supplied five separate canaries, absent from its three operational records. These finite tests support the observed exclusion behavior. They do not prove that every identifying value is excluded: an accepted identifier can itself carry information.

That boundary is visible in D's first case. The format-valid req-deadbeef value remains unchanged. Its record carries provider_request_id_privacy=format_checked_only_not_anonymized. This label explains the limit; it does not anonymise the identifier. Missing IDs are labelled not_recorded.

OWASP's logging guidance describes data that should usually be excluded or protected and calls for validation of untrusted event fields. The fixture covers a narrow collection path. It does not test a whole logging policy, legal basis, retention system or access-control implementation.

Preserve unknown values instead of inventing measurements

The snapshot fixture supplied 12 input and 4 output tokens. With invented rates of $1 per million input tokens and $2 per million output tokens, the estimate is (12 + 2 × 4) / 1,000,000 = $0.00002. That is arithmetic on synthetic values, not a vendor price or bill.

The alias-without-usage, error and timeout records retain null token counts and null cost. A timeout does not tell the caller whether a provider did billable work. A returned alias also leaves the underlying revision unknown; even the mock snapshot label is only provider-claimed.

The rate-limit case recorded Retry-After=2 seconds, but performed no retry. The attempt=1 field is a constant in this adapter, not a measured retry count. D makes that meaning explicit with attempt_basis=constant_single_attempt_adapter. Actual request counts come from the server observations.

A failure before any HTTP status

In D's third case, the mock accepted the request and destroyed its connection before sending headers. The adapter recorded transport_error at request stage with http_status=null. Without packet capture, we do not call this an observed TCP RST or claim that it covers every transport failure.

D's second case returned HTTP 500. Excluding its error body protects the collection boundary but loses diagnostic detail: the operational record cannot explain the service's underlying failure. A separate diagnostic capture channel would need a defined purpose, restricted access and expiry. No such channel was built or tested here.

Apply the result to an incident record

Start with the decision you need to make. Was the failure before headers, while reading the body, while decoding, or while validating the response? Record application and template versions, permitted correlation identifiers, the requested and returned model labels, outcome, failed stage and available timing or usage values.

For retrieval and tools, define equally limited references and outcomes. A document identifier or destination can be sensitive even without the document contents. Record a reason for each field, who may read it and how long it is retained. If output quality is the question, use a permitted feedback reference or a separately approved diagnostic sample; a success event is not a correctness judgment.

Use a synthetic fault case to check that another engineer can locate the failed stage. Then separately test expiry, access controls and exporter behavior in the intended system. Those controls are work still to do, not consequences of this fixture passing.

Verify the stored evidence

Download the reader evidence package, extract it, and open index.html to browse the separate C and D records. With Node installed, run `node check-files.mjs` from the package root. It checks the file list, sizes, hashes and copy map only. It does not invoke the original C/D semantic validators, re-evaluate their stored assertions, make HTTP requests or rerun the experiments. The scripts and the sibling dependency used by D are retained for inspection; the checksum command does not execute them.

The raw fixture records intentionally contain fabricated payloads excluded from the ordinary log. Do not use that capture harness unchanged with real traffic. The manifest is unsigned and cannot establish authorship or trusted chronology. Completely rewriting a package and its hashes remains outside its protection.

Large bodies, concurrency, retries, disk failures, exporters, retention, access controls and real-provider behavior were not tested. The linked MDN and OWASP documentation was reviewed on 15 October; it was not fetched again for the 17 October reader export; the experiment dates and historical source records remain unchanged. The evidence package is now published. Publication does not establish measured readership.