Packages

A real-browser external-app verifier for patch-safe Phoenix LiveView interactions, with secondary unstyled reference components.

Current section

Files

Jump to
live_interaction_contracts CHANNEL_CONTRACT.md
Raw

CHANNEL_CONTRACT.md

# CHANNEL_CONTRACT (v1, frozen)
A Channel pairs a client-owned input atom (a query buffer) with server-owned result
content, across the network boundary, without letting stale results be treated as
current.
## The definition that matters
> **A Channel is not merely a version stamp. A Channel requires the server render path
> to enforce monotonicity; otherwise stale results can still enter the DOM.**
This is the frozen upgrade from the earlier "version stamp" framing. The spike proved
the distinction empirically (guard on vs off).
## Contract
A conforming Channel MUST:
1. **version-through-render.** The client stamps each query emission with a
monotonically increasing version. The server stamps each result render with the
version it answers, in the rendered markup (so the version round-trips through the
render, not just the event).
2. **server-enforced monotonicity (the construction-grade requirement).** The server
render path MUST refuse to render results for a superseded version. This is a
**server obligation** — the guarantee lives in server-side logic, not in a stamp.
3. **client-side stale detection as a secondary guard only.** The client MAY compare
rendered version to last-emitted version and mark staleness, but this is
**convention, not construction**: without requirement 2, stale results still enter
the DOM and are only flagged after the fact.
4. **not emit during IME composition.** No query snapshot is emitted while a
composition session is active on the input; exactly one versioned emission may
follow `compositionend`. *(The rule is frozen; real-device IME verification is a
`BACKLOG.md` item — the current evidence is synthetic events only.)*
## What the harness can and cannot verify
The harness verifies that the **reference fixture** upholds the contract (server drops
superseded versions; client detects the convention-only case). It **cannot** verify
that an arbitrary application upholds requirement 2 — a developer who omits the server
guard silently drops to convention-only correctness. This limit is intrinsic: the
guarantee is a server obligation, not a framework feature.
## Why it stays in the kernel
Combobox correctness is impossible without it, and the version must round-trip through
the server render — coupling it to the rendering pipeline in a way generic
application-level request/response versioning is not. It is the kernel's least
ergonomic member (the guarantee is developer-upheld), but not reducible to Cursor or
Delegation.
## The shipped helper (v1; upgrades convention → construction)
The early caveat — "the guarantee is developer-upheld" — is now closed by library
code in the v1 surface: `LiveInteractionContracts.Channel` ships the server-side
monotonic guard.
- `Channel.new/0` → put in an assign.
- `Channel.note_query(ch, v)` → call on every incoming versioned query.
- `Channel.accept(ch, v)` → THE guard, called on the render path: `{:ok, ch}` renders
and stamps `rendered_version`; `{:stale, ch}` refuses (counted in `stale_drops`).
A LiveView that routes every result render through `accept/2` gets prevention **by
construction**; the component stamps `data-version={rendered_version}` so the client
can still detect (as telemetry, never authority) if an app bypasses the guard. The
combobox reference suite asserts both directions: guard-on drops the stale version
server-side; the fixture's guard-off negative control shows the stale render reaching
the DOM with only client telemetry flagging it — i.e. the helper is precisely what
buys the construction-grade guarantee.
*Evidence: `reports/COMBOBOX_SPIKE_REPORT.md` (test 2, and tests 3–5 for Cursor × Channel
composition).*