# A consumer's test harness has no way to play several identities Raised by the first consumer (Festipod) on 2026-08-10, while migrating onto `@ng-eventually/sdk` against `contract_sdk-surface` @ `30f6263`. Three findings, one substantive and two defects in the contract's own text. ## 1. The substantive gap — a consumer harness cannot switch identity `contract_sdk-surface` § *Guarantees* states the whole of signing in: `ensureIdentity()`, which takes no identifier, deliberately. The API contract adds, about `setCurrentUser`'s removal, that *"the e2e harness plays several identities on one page and reaches it by its internal path, which is what a harness is allowed to do and an application is not."* That sentence holds for **this library's own** harness. It does not hold for a consumer's, and the package makes sure of it: `packages/sdk/package.json` maps exactly one entry, `"." : "./src/index.ts"`. A deep import is refused by the resolver, verified from the consumer's tree: ``` ROOT OK: configure, docChangeType, docs, ensureIdentity, inbox, init, initNg, ng, readUnion, storeRegistry, subscribeDoc, subscribeDocs, useShape, watchShape DEEP FAIL: Cannot find module '@ng-eventually/sdk/src/shared-wallet/access-gate' ``` So a consumer's multi-actor suite has **no path at all** — published or internal — to act as a second identity. The consequence is not cosmetic: a test that cannot obtain a second actor is forced to hand the first actor's values across the identity boundary through a shared variable, which is precisely the shape that hid a real bug in this library once already (a third party never reached the owner's inbox, and the green test proved nothing because the address crossed the boundary by JS scope). Losing the ability to write that test correctly costs more than the surface it saves. What the consumer needs is narrow: **act as identity X for the duration of a block, then restore**. It is a harness capability, not an application one — the request is not to re-publish `setCurrentUser` on the application surface. A separate, explicitly-named test entry (`@ng-eventually/sdk/testing`, say) would keep the application surface exactly as it is while making the capability reachable; it would also carry its own deletion signal, since a consumer harness that plays several identities on one page is itself pure shared-wallet scaffolding. Not proposing the shape — this is the library's call. Stating the need, and that it currently has no answer. ## 2. `watchShape`'s published signature contradicts its own types `## Surface` publishes: ```ts export function watchShape(query: ShapeQuery): ShapeObservable; ``` `ShapeQuery` is the **result** type (`{ data, isPending, isSuccess, isError, error }`, per `docs/api-contract.md` § 5), so as written the call takes its own return value. The signature the consumer has always called, and the one § 5 documents, is `watchShape(shapeType, scope)` — two positional arguments. One of the two documents is wrong; the contract is the one consumers read. ## 3. There is no synced read for the per-document form The inbox surface publishes `readSynced(targetInbox)` and `readForDocument(doc)`, but not their intersection. The consumer's materialization path depends on the **synced** guarantee specifically (`read` and `readSynced` differ by contract), and it addresses by document. Today it must therefore resolve an address itself to get the synced form — which is the exact gesture § *Guarantees* says an application never performs (*"an application never handles a key or an inbox address"*). Either `readForDocument` carries the synced guarantee, or a `readSyncedForDocument(doc)` completes the pair. As it stands the document-addressed path is strictly weaker than the address-addressed one, and the contract does not say that is intentional.