Skip to content

Conformance subject for the RFC 0001 relay kit, and graceful draining - #11

Merged
Mathieu2301 merged 3 commits into
mainfrom
mathieu/conformance-subject
Sep 14, 2026
Merged

Mathieu2301 merged 3 commits into
mainfrom
mathieu/conformance-subject

Conversation

@Mathieu2301

@Mathieu2301 Mathieu2301 commented Sep 13, 2026 •

Copy link
Copy Markdown
Member

Companion to the relay conformance kit in Miakapp-V3#184. This relay is its first subject, and passes all thirty-seven scenarios.

Why

RFC 0001 §14 delegates conformance items 6 and 7 — session-layer direction and state rules, and outcomes under fault — to each implementation's own tests. A second implementation therefore has nothing shared to be certified against for exactly the two items that distinguish a server from a codec.

The subject contract

test/conformance-subject prints one LISTENING <ws url> line, serves RFC 0001 at /ws, accepts a closed fixture credential set, applies the profile named by MIAKAPP_CONFORMANCE_PROFILE, and exits cleanly on signal.

The fixture verifier lives in this separate main package, so no build of cmd/miakapp-server can contain it.

Two rules the fixture set must mirror

Both produce an opaque authentication failed rather than a useful error when you get them wrong, which cost me a debugging round:

  • a user identity carries no Home Key client binding;
  • a coordinator or CLI identity carries a client binding and no verified email.

The relay change: graceful draining

Writing the corpus's drain scenarios surfaced a real gap rather than a missing test. RFC 0001 §6 gives the session layer a DRAINING state, §7.1 gives the relay GOAWAY to enter it, and §12 asks the relay to send GOAWAY or FATAL before closing when possible. Here, phaseDraining was reachable only inside the rule that refuses new requests while draining — a state no code path could ever reach — so a shutdown dropped sockets without announcing anything.

Server.Drain announces the window to every authenticated session, moves each to DRAINING, and closes whatever is still open when the window expires, so a peer that ignores GOAWAY cannot hold a restart open. Details worth a reviewer's attention:

  • The announcement is queued before the phase changes, or the draining rule it installs would refuse the frame that installs it.
  • Draining is one-way, and a session authenticating mid-window is drained as soon as its bootstrap is on the wire, so nobody slips past by arriving late.
  • Terminal call frames stay allowed throughout. That is what makes the window worth having: a call already dispatched resolves normally instead of becoming an OUTCOME_UNKNOWN that §8.3 forbids a client from retrying.
  • The connection registry this needs replaces the bare WaitGroup. Close keeps exactly its previous semantics.

Four TestDrain* tests in internal/relay/drain_test.go pin the window: the announcement and the refusal that follows it, an in-flight call reaching its terminal reply, the deadline close, and a session that authenticates mid-window.

Two subject additions the expanded corpus needs

  • The drain-on-cli profile. A drain is the one relay behaviour no peer can request on the wire, so the kit needs an ordered trigger. This subject starts its window when the CLI fixture credential authenticates: no other scenario uses that credential, verification is the one point the subject observes before a session exists, and a short delay keeps the CLI's own WELCOME ahead of the announcement — which beats racing a timer against a twenty-frame prelude.
  • A coordinator fixture with a different Home Key client binding, so a scenario can show that reauthentication renews authentication material without moving a session to another client. The corpus already covered the home and principal halves of that rule.

Verification

gofmt -l ./internal ./test                        # clean
go vet ./...                                      # clean
go test ./...                                     # green
go test -race ./internal/relay/                   # green
bun run src/main.ts -- /tmp/conformance-subject   # 37/37

Control for the drain scenarios: built against a subject whose drainProfiles set is empty, all four fail with timed out waiting for a frame. The assertions depend on the relay actually sending GOAWAY, not on the scenario shape.

🤖 Generated with Claude Code

Mathieu2301 and others added 3 commits September 13, 2026 16:47
RFC 0001 §14 delegates conformance items 6 and 7 — session-layer direction and
state rules, and outcomes under fault — to each implementation's own tests, so
a second implementation has nothing shared to be certified against. The relay
conformance kit in Miakapp-V3 makes them shared; this is the adapter that lets
this relay be its first subject, and it passes all eleven scenarios.

The subject prints one LISTENING line, serves RFC 0001 at /ws, accepts a closed
set of fixture credentials and applies the profile named by the environment.
The fixture verifier lives in this separate main package, so no build of
cmd/miakapp-server can contain it.

Two rules the fixture set has to mirror, because getting either wrong produces
an opaque authentication failure rather than a useful error: a user identity
carries no Home Key client binding, and a coordinator or CLI identity carries a
client binding and no verified email.

Co-Authored-By: Claude Opus 5 <[email protected]>
RFC 0001 §6 gives the session layer a DRAINING state and §7.1 gives the relay
GOAWAY to enter it, but nothing here ever entered it. phaseDraining was
reachable only in the rule that refuses new requests while draining, so the
state machine had a state no code could reach and a shutdown dropped sockets
without announcing anything. §12 asks for the opposite: send GOAWAY or FATAL
before closing when possible.

Drain announces the window to every authenticated session, moves each to
DRAINING, and closes whatever is still open when the window expires, so a peer
that ignores GOAWAY cannot hold a restart open. The announcement is queued
before the phase changes, or the draining rule it installs would refuse the
frame that installs it. Draining is one-way, and a session that authenticates
mid-window is drained as soon as its bootstrap is on the wire, so nobody slips
past the announcement by arriving late.

Terminal call frames stay allowed throughout, which is what makes the window
worth having: a call already dispatched resolves normally instead of becoming
an OUTCOME_UNKNOWN that §8.3 would forbid a client from retrying.

The connection registry this needs replaces the bare WaitGroup. Close keeps
exactly its previous semantics.
Two additions the expanded corpus needs from its first subject.

A drain is the one relay behaviour no peer can request on the wire, so the kit
needs an ordered trigger. Under the drain-on-cli profile this subject starts its
window when the CLI fixture credential authenticates: no other scenario uses
that credential, verification is the one point the subject observes before a
session exists, and a short delay keeps the CLI's own WELCOME ahead of the
announcement. That beats racing a timer against a twenty-frame prelude.

The other addition is a coordinator fixture carrying a different Home Key client
binding, so a scenario can show that reauthentication renews authentication
material without moving a session to another client. The corpus already covered
the home and principal halves of that rule; this completes it.

Both live in this separate main package, so no build of cmd/miakapp-server can
contain them.
@Mathieu2301 Mathieu2301 changed the title Conformance subject for the RFC 0001 relay kit Conformance subject for the RFC 0001 relay kit, and graceful draining Sep 13, 2026
@Mathieu2301
Mathieu2301 merged commit dca95b6 into main Sep 14, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant