maxPayloadBytes

open val maxPayloadBytes: Int?

The largest payload a single broadcast or sendTo may carry, or null when this seam cannot tell.

null means unknown, not unbounded — it is the honest answer from a fabric with no frame ceiling it can name, and a caller must treat it as "no guidance", never as "any size is fine". A non-null value is a promise: a payload of that size or smaller will not be refused by this seam for being too big. (It may still fail for every other reason a send can fail.)

Why the contract needs this at all

A framed fabric rejects an oversize frame — :kuilt-stream's framed() throws FrameTooLargeException — and every layer that wraps a payload before handing it down spends some of that ceiling on its own header. Without a published limit those two facts meet only at run time, in the failure: a payload that fits when sent directly overflows once a decorator wraps it, and the caller learns the difference from a fabric-level error it had no way to anticipate (#2047). Publishing the number lets a wrapper subtract its own cost and hand the caller a bound it can actually respect.

What a decorator owes

A Seam that decorates another and adds bytes reports the delegate's limit less its own overhead, floored at zero — the idiom inner.maxPayloadBytes?.let { (it - cost).coerceAtLeast(0) }. Subtract unconditionally, even when the overhead is only paid on some routes: a limit that moves with routing is a TOCTOU trap, because the route can change between the caller's check and its send. A stable, conservative bound is the only useful one.

A decorator that adds no bytes delegates unchanged. Leaving the default in place is safe but lossy — it discards a bound the fabric underneath does know.

An implementation that publishes a limit should refuse an over-budget payload with PayloadTooLarge rather than letting a fabric-level error out, subject to each method's own contract: sendTo is addressed and reports, broadcast is best-effort and drops.

Publishing IS enforcing, and the check is per link (#2069)

The in-tree fabric seams pre-check every send. They check the connection's us.tractat.kuilt.core.fabric.Connection.maxFrameBytes, not this value: a link's ceiling is fixed for the life of the link, whereas this aggregate moves, so checking here would leave a check-then-send window a tightening could slip through — and on a mesh it would refuse a payload the addressed link could carry. A decorator with no connection under it has no such alternative and checks this value; the difference is what it can see, not what it owes.

What that replaced is worth stating, because it is the failure mode a published-but-unchecked budget produces anywhere: LinkSeam.sendTo enqueued and returned success, and its write loop — which cannot tell an oversize frame from a dead wire — then tore the whole seam down asynchronously, after the caller had been told the send was accepted; MeshSeam.sendTo routed the same failure into removePeer, evicting a healthy recipient as though its link had died. Publishing a number nothing checks is worse than publishing none.

The TCK now holds every fabric that publishes a number to it at both edges.

#2058 closed the publishing half for the decorators — a mux channel view, TieredSeam, a CompositeSeam, RoomHubSeam, a gossip overlay and its per-peer view all derive a number from what they wrap instead of reporting null. The enforcing half stays open under #2642: none of them pre-checks against its own value, so an over-budget send is still refused — by the seam underneath, against its budget — and the PayloadTooLarge a caller sees names a number larger than the decorator published, by exactly the framing the decorator reserved. That is a milder failure than the one this section describes (the refusal is synchronous and the send never reported success), and it errs toward refusing less than promised rather than more, but it is not the contract.

NwSeam is where the distinction was drawn, and it is worth keeping. It enforced a 16 MiB ceiling and refused above it while publishing nothing — deliberately, because publishing is a promise to carry, not merely to refuse, and its receive path dropped bytes under a multi-chunk burst, so a frame that size never completed. A fabric may enforce a bound it cannot yet promise; those are different claims, and only the second one this value makes. The withholding ended with #2134, which gave the receive path real backpressure — so NwSeam now publishes its ceiling, and the TCK's payloadOfExactlyTheBudgetIsCarried (the case that found the defect) is what holds it to it.

A reading, not a lease

This value may move. A mesh reports the minimum across its live links, so a peer attaching over a tighter transport lowers it. Read it per send; a caller that reads once and trusts the value for a whole batch can be refused part-way through, correctly.