The session
startSurface binds an ephemeral port on 127.0.0.1. The page URL
carries a session token in its fragment. Every API request must send
that token as a bearer token, checked in constant time. Requests with a wrong Host, or a wrong Origin on a state-changing
request, are refused. JSON bodies are bounded, responses carry strict
security headers, and response values pass secret redaction by default;
a handler that returns { verbatim: true }, like a completion with the
verbatim option, sends its content byte-exact.
A session ends exactly once: the app completes it with a payload, the
user cancels it, the lease or session times out, or the caller
interrupts it. The browser acknowledges the decision, and a timeout
covers a browser that never does. waitForDecision() gives the caller
the one result. Payloads pass secret redaction unless the app completes
with an explicit verbatim opt-in for content that must not be redacted,
such as review annotations that quote code (the review still trims and
caps them before completing). A payload is its observable own data: the
values of its own enumerable string-keyed properties and array items as one
JSON serialisation reads them (a getter’s answer or the result of a
callable toJSON included) recursively, and that is exactly what the
caller receives.
Hidden internal state (a Map’s entries, a URL’s address) is not data and
is neither carried nor promised; an object with its prototype removed is
carried as its observable data. (A review location is stricter: the
contract keys an anchor by own data properties only, so an accessor or a
toJSON there is no location.) When two
authenticated requests race to complete, exactly one succeeds; the other
receives a conflict. A request whose body is still arriving when the
session closes is refused before its handler runs. A completion is
serialised exactly once when it is claimed, and the plain snapshot of that
serialisation is what the caller receives: a payload JSON cannot carry
whole (a cycle, a BigInt, a function, a symbol, an undefined value, a
non-finite number, negative zero, a non-enumerable or symbol-keyed
property, a hole or extra property in an array, a Proxy, or an object whose
prototype is neither Object’s nor none, such as a Map, a RegExp, an Error,
or a class instance, whose meaning is not in its own data) is refused with
an error and the session stays open. One exception is deliberate and takes
precedence: a value whose toJSON property is callable (own or
inherited, a Date for instance) is replaced by whatever toJSON returns
before anything else looks at it, and that return value is what is checked
and carried. This holds even when the holder is a Proxy or has a foreign
prototype: only what toJSON returned is traversed, and it must pass the
same rule. The browser is never told a
session completed
that the caller will never receive, and a value that would serialise
differently later cannot change the frame. Once a handler has completed the session,
its response is the fixed acknowledgement (ok and the operation id) and
a body or status it returns after completing is not used. That answer is
sent the moment the completion is claimed, so a connected browser holds
its operation id before the caller can learn of the completion, even when
the handler keeps running or never returns. The acknowledgement timeout
starts when that answer has gone out. If the browser’s connection closes
before the answer arrives, the timeout starts at the disconnect instead,
and when it elapses the caller learns of a completion that browser never
received. The session never waits without a bound. A cancel is answered
the same way before the caller learns of it. An app can pass terminalPayload(status) to
startSurface so a session that ends without the app’s completion
(cancelled, timed out, or interrupted) still carries an outcome payload the
caller can route. It passes redaction unless the app also sets
terminalPayloadVerbatim, which an app needs when the payload carries
identity fields that redaction would rewrite. Without a hook, those statuses
carry a null payload.
The handoff
The result returns through app-named frames on stdout, so one caller can run several surfaces and demultiplex them. Content that must move as files travels through a private temporary directory (0700, files 0600) with SHA-256 hashes in the manifest, so the consumer verifies bytes before use. Diagnostics stay on stderr.The client kit
@obversa/surfacer/client is the no-framework browser kit: it reads
the token from the fragment, removes it from the address bar, keeps the
lease alive with a heartbeat, wraps every call with the bearer token,
and acknowledges the terminal decision on submit or cancel.
The launcher
runSurface is the one call an app makes: it starts the session,
places the page, waits for the decision, frames it on stdout, and shuts
down. Signals interrupt cleanly. The runnable example is
packages/surfacer/examples/hello-surface.mjs.
Placement
openSurfaceUrl opens the page in the selected host’s native pane.
It tries cmux first, through the host glue. The platform browser
is the fallback on macOS, run by its system path (/usr/bin/open) and
never by a bare name; on Linux and Windows the URL is printed instead
(xdg-open runs its own helpers by bare name, and cmd /c start would
take the URL as shell text), and printing the URL is the last resort
everywhere. Your
multiplexer supplies the panes; the Surfacer supplies the session. The
host’s placement command is the one the host injected as
OBVERSA_SURFACE_BIN, and it runs only when that value is an absolute
path. What it receives is a one-time launch URL (single use, valid for
a minute, answering with one redirect to the page), never the page URL
with the session’s token in it, since a process argument is readable by
any local process; a bare name looked up on PATH would still hand that
one door to whatever executable sat first on it. A relative or bare
value is reported on stderr and skipped,
and an unset value skips host placement altogether. A
placement command that exits reports whether it succeeded. One that is
still running five seconds after launch (a browser that keeps the tab’s
process) is taken to have opened the page; that is an assumption, so a
command that fails only after those five seconds is not reported as a
failure.