iOS network capture wire contract¶
SdkNetworkRequestEvent is the network wire payload for #5158. The SDK JSON-encodes it into SdkEventEnvelope.payload (base64), wraps envelopes in SdkEventBatch, and sends batches to CtrlProxy. CtrlProxy stores and returns the envelopes through /sdk-events without projecting the network fields. decodeSdkEventBatches is the production TypeScript retrieval decoder; DefaultIosSdkEventIngestor validates the version before recording anything as network traffic.
Capture requires host registration through the host-integration profile: register a URLSession configuration (the URLProtocol for default and custom sessions, delegate forwarding for background ones), or register and call the WebSocket/NWConnection adapters from the host’s transport callbacks. Enabling capture does not instrument unregistered transports. Hosts must redact endpoint/query/metadata values before supplying them; no automatic URL or metadata sanitization is implied.
Versions and diagnostics¶
The SDK’s named constant is SdkNetworkRequestEvent.currentSchemaVersion = 1; new events always encode the integer schemaVersion. The initializer’s default preserves existing call sites. The custom decoder used by EventPersistence.loadPending accepts missing versions as legacy v0 and re-encodes that version explicitly. TypeScript mirrors support in IOS_SDK_NETWORK_SCHEMA_VERSION.
Absent versions and explicit v0 are accepted. v1 is current. Future versions (>1), negative numbers, fractional numbers, null, and non-numeric values are rejected before network persistence and network telemetry push. Legacy protocol remains a fallback when protocolName is absent. This is version validation, not a new validator for every historical optional field.
Rejection creates a typed diagnostic with code: "sdk_network_schema_unsupported", receivedVersion, supportedVersion, and bundleId. It is logged with logger.warn and recorded as a warning telemetry log (level: 5, tag: "SdkNetworkWire", filterName: "sdk_network_schema_unsupported"), with the diagnostic JSON in message. This uses existing persisted log telemetry and the log category of the telemetry push/backfill channel, so consumers can observe it even when network capture is disabled. Ingestion never throws on a version mismatch. No network row is inserted. Version and event type are routing controls, not network data columns; no new migration is needed.
Rate-limited rejection diagnostics¶
Payloads from a newer SDK schema version are rejected with one diagnostic per device/app/version per
10-minute window. The first has no suppression count; the next rejected event after the window
re-emits with suppressedCount for events suppressed since. Received versions are bounded to 32
characters (ellipsis when truncated). Each ingestor tracks at most 64 keys; the last slot is a shared
overflow key that rate-limits additional keys together.
Field mapping¶
TelemetryRecorder.recordNetworkEvent forwards every data field below to the repository and telemetry push. automobile:network/traffic (including live/error/slow lists) carries summary fields; headers, bodies, and body sizes are exposed only by automobile:network/request/{rowId} (detail). Both use the TS names. Request resource IDs are database row IDs, distinct from SDK requestId. Optional null fields remain nullable; legacy rows continue to read.
| Wire key | Swift property | TS ingestion/output | Column | MCP surface |
|---|---|---|---|---|
| eventType | eventType | network_request routing | none | selects network category |
| schemaVersion | schemaVersion | version gate | none | mismatch log diagnostic |
| timestamp | timestamp | timestamp | timestamp | traffic + request |
| url | url | url | url | traffic + request |
| method | method | method | method | traffic + request |
| requestId | requestId | requestId | request_id | traffic + request |
| connectionId | connectionId | connectionId | connection_id | traffic + request |
| direction | direction | direction | direction | traffic + request |
| protocolName | protocolName | protocol (legacy protocol fallback) | protocol | traffic + request |
| metadata | metadata | metadata | metadata_json | traffic + request |
| sequenceNumber | sequenceNumber | sequenceNumber | sequence_number | traffic + request |
| requestHeaders | requestHeaders | requestHeaders | request_headers_json | detail |
| requestBodySize | requestBodySize | requestBodySize (absent: -1) | request_body_size | detail |
| statusCode | statusCode | statusCode (absent: 0) | status_code | traffic + request |
| responseHeaders | responseHeaders | responseHeaders | response_headers_json | detail |
| responseBodySize | responseBodySize | responseBodySize (absent: -1) | response_body_size | detail |
| durationMs | durationMs | durationMs (fallback: finite metadata.duration_ms, then 0) | duration_ms | traffic + request |
| error | error | error | error | traffic + request |
| host | host | host | host | traffic + request |
| path | path | path | path | traffic + request |
| requestBody | requestBody | requestBody | request_body | detail |
| responseBody | responseBody | responseBody | response_body | detail |
| contentType | contentType | contentType | content_type | traffic + request |
Redaction and bounds¶
NetworkCaptureRecorder.redactHeaders replaces authorization, proxy-authorization, cookie, set-cookie,
x-api-key, x-auth-token and x-csrf-token values (case-insensitive) with <redacted>. Adapters apply
it, and so does the shared AutoMobileNetwork.recordRequest emission boundary, so manual,
task-delegate and URLProtocol callers get the same policy. Headers still require opt-in capture.
Recorder bodies are bounded by maxBodyBytes (default 32 KiB); the shared emitter applies
truncateBody again with its configured bound before events enter the SDK buffer. Body capture is
opt-in and disabled in release builds. TS repository reads apply the surrogate-safe truncateBodyText
cap of 10,240 UTF-16 units to traffic and request reads; original body sizes survive. TS adds no new
storage bound and preserves SDK metadata unchanged. These bounds imply no body-value redaction or
metadata sanitization.
NetworkState notifications contain only resource URIs and trigger a fresh read; its internal debounce/filter record is not a separate payload read surface. The resource read and telemetry data paths retain the fields above.
Serialized contract fixtures¶
The sole fixture source is NetworkCaptureRecorderTests.testWritesSerializedNetworkCaptureFixtures. Regenerate from the repository root:
cd ios/auto-mobile-sdk
AUTOMOBILE_FIXTURE_OUT_DIR=../../test/fixtures/ios-sdk-network swift test --disable-sandbox --filter NetworkCaptureRecorderTests.testWritesSerializedNetworkCaptureFixtures
Copy output verbatim; generation provenance is documented in test/fixtures/ios-sdk-network/README.md. Never hand-author SDK-encoded fixture JSON. urlsession, websocket, and nwconnection cover the three adapters; urlsession-full and urlsession-error add bounded bodies, response headers/content type, and an HTTP error with top-level timing. Host callbacks enrich adapter records with top-level timing/content type and observed status because the adapter itself does not currently supply them. The error fixture has every network data property non-nil. A WebSocket host supplies its observed handshake status.
IosSdkNetworkPersistence.contract.test.ts enumerates event batch fixtures (excluding generated contract-keys.json) and exercises the production batch decoder → real telemetry recorder → injected in-memory DB → repository list/detail → registered MCP traffic/request handlers and telemetry push. It compares the explicit column map with the generated Swift CodingKeys list, catching unmapped fields even when always nil, and a non-null union guard requires fixture examples for every property. Version test variants derive legacy/current/future payloads from real fixture JSON, without claiming those variants are SDK-encoded files. Decoder unit tests cover malformed envelopes, clock fallback, order, and poll generation cancellation.
bun test test/features/observe/ios/IosSdkNetworkPersistence.contract.test.ts
bun test test/features/observe/ios/decodeSdkEventBatches.test.ts
bash scripts/ios/api-dump.sh --check