# ADR 0007: caller-owned .NET publisher and browser SDK - Status: Accepted - Date: 2026-07-16 - Tracking: #9 ## Decision The .NET client package exposes separate publisher and browser interfaces plus concrete clients over a caller-supplied `HttpClient`. The caller owns that client, its handler, base address, connection pool, proxy, and lifetime. SDK operations dispose every request, response, and response body they create, but never dispose the supplied client. The package targets `netstandard2.1`, depends only on the wire-contract package and LiteNetLib, and contains no Godot types, global client, service URL, publisher secret, or embedded game credential. Every operation returns `RendezvousClientResult` with a stable error code, message, and optional retry guidance. Cancellation remains exceptional through the caller's `CancellationToken`; transport failures become `ServiceUnavailable`. Response bodies are streamed under the contract's 256 KiB browser ceiling before deserialization. Invalid or oversized success bodies become `InternalError` and never escape as partially trusted contract objects. The SDK retries only operations whose duplicate execution is safe: scoped reads, idempotency-keyed registration, lease renewal with the same lease token, complete resource update, and lease-token deregistration. It honors bounded server retry guidance and otherwise uses capped exponential backoff with jitter. Each retry creates a fresh HTTP request while preserving the caller's registration idempotency key. Configuration is copied on construction so later option mutation cannot change an in-flight client's behavior. `PublishedSession` holds the server-issued lease and presence capabilities needed by the host. Its string representation always redacts them. Update requests are copied before the lease token is attached, so the SDK never mutates caller-owned DTOs. The browser exposes one-page calls and bounded cursor traversal; cursor values remain opaque and caller requests remain unchanged. Lease maintenance is explicit. Creating a `SessionLeaseMaintainer` starts no task; the game chooses when to call `RunAsync`, owns cancellation, and awaits `DisposeAsync`. The loop uses the latest server-provided renewal interval and returns a distinct cancelled, disposed, lost-lease, or failed result. Terminal authorization, expiry, and missing-lease responses also raise `LeaseLost` so the host can stop advertising or re-register deliberately. ## Consequences - SpaceGame and Unscouted can inject the publisher/browser interfaces in tests without an engine runtime or real network. - Games must configure an absolute `HttpClient.BaseAddress` (or equivalent handler routing), obtain publisher credentials from their deployment boundary, and explicitly run and dispose lease maintenance. - The versioned client public-API snapshot and live-server integration tests fail together when SDK and HTTP contracts drift.