feat(protocol): cap outbound bodies before they reach the wire #63
No reviewers
Labels
No labels
priority/P0
priority/P1
priority/P2
release/v0.1.0
status/blocked
status/planned
type/bug
type/design
type/test-gap
type/tracker
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis!63
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/outbound-limits-16"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #16.
Summary
Senders now enforce the limits receivers already apply, and each operation
carries a semantic cap instead of inheriting the 64 MiB framing ceiling.
felis-protocolgainsmessages/limits.rs: per-operation caps (bridgelines, input/paste bytes, argv/environment, search patterns, retarget
descriptors) plus a
Validatepass run before a body is narrowed tou32or a header is written. Frame encoding and writing are fallible.
felis-daemonholds every outbound event under the frame ceiling, so alarge region reply or stream chunk is refused as a typed error rather than
silently dropped mid-stream.
felis-clirefuses over-limit payloads before dialing, reading one bytepast the cap so the boundary is decided without holding the payload.
felis-client/felis-client-corerefuse before committing side effects:a search query is validated before its stream id is opened, a request is
validated before its id is issued, and the pipe chord's
pastesink runsthe same admission (and one-shot visible notice) as the clipboard and
file-drop paths — an over-limit paste is refused, never silently truncated
or dropped.
Doc cascade
docs/reference/ipc.md(per-operation limit table beside the framing cap),docs/reference/cli.md,docs/reference/spec.md,docs/reference/testing.md,docs/explanation/security-model.md,docs/explanation/input.md,docs/explanation/data-model/scrollback.md,CHANGELOG.md, and the product-shippedskills/felis/SKILL.md.Verified: just check green (fmt, clippy, nextest, deny), pi review PASS
A diff cycle ships every dirty row in one `RowDelta`, and at the extreme geometry (2048x2048) a full-screen redraw of wide clusters can approach the 64 MiB frame ceiling. Now that the writer refuses such a body, an unsplit batch would cost the subscriber its connection, so a batch past half the ceiling is emitted as consecutive frames: rows are keyed by index and apply independently, so the reader reaches the same screen either way. Half, not the whole ceiling, because the protobuf field framing and the registry entries riding beside a batch cost bytes the row-payload sum does not see. A row batch is not the only body that grows without a bound of its own: a `RegionMsg::Reply` stitches a whole region into one `Vec<u8>`, and 10 000 scrollback rows of 2048 wide colored cells (REQ-605, REQ-605a) serialize past the ceiling, so a `pipe` chord over a large buffer would hit the same writer refusal and lose the window its connection mid-action. It cannot split -- the reply is one blob by contract -- so it is refused instead, as the request's own `Conn::Error { InvalidRequest }` naming the size and the budget. Truncating to fit was the alternative and is worse: a pager, a file and the clipboard all show a fragment as if it were whole, and `felis sessions capture` already reads the same region row by row with no ceiling. The remaining refusal path is a daemon bug -- nothing a subscriber sends makes an event too large -- so it is logged at `error` with the kind and length before the eviction, or the eviction reads as a client fault in the log. Refs #16 Assisted-by: Claude Code