Follow felis to 105b0899: wire-baseline reset, .fcast envelope v2 #1
Loading…
Reference in a new issue
No description provided.
Delete branch "follow-felis-105b0899"
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?
Follows felis to
105b089979369fd310757375af3df7900ae7e523(105b0899), fromthe pin at
32775067(2026-07-16). felis reset its wire-break baseline to 1.0(
f56f5529) and grew toPROTOCOL_MINOR10, so this is renames and removals,not additions — the old pin names a protocol that no longer exists.
Two commits: the code adaptation, then the doc cascade.
What felis changed, and what it forced here
Capability/encoding negotiation is gone. No
Capabilitiesbitfield, noEncodingenum, noSCROLL_OP, no self-describing tagged handshake. Aconnection is now a frozen 8-byte preface agreeing a major and an effective
minor, and the wire carries protobuf alone.
recorddialsconnect(.., Offer::window(false))instead of naming anencoding preference.
Windowis deliberate: it is the only mode felisstreams a grid to, so a recording of anything less is not a recording of the
session.
serveanswers the preface on the raw socket halves withDaemonPreface::select(refusing an unservable major as a daemon does) andholds its
FrameWriterat the negotiated minor, so every frame it writespasses the same send gate a real daemon's does. Its advertised roster row is
shaped for that minor —
SessionInfo.sequenceonly at minor ≥ 3 — so theroster is never refused over a field the peer never asked for.
Welcomecarries
identityonly at minor ≥ 9.That removal reaches the
.fcastheader, which recordedproto_version(felis's retired
WIRE_GENERATION) pluscapabilitiesandcapability_bits.None of the three has a counterpart to carry forward, so the header now
records
proto_majorandproto_minor— the effective minor, not therecorder's build, since the bodies can only hold what both peers agreed on.
Removing header fields is a breaking envelope change by
docs/reference/format.md"Versioning", soSUPPORTED_VERSIONis 2 and v1files are rejected rather than half-interpreted. The decision, including why
refilling
proto_versionwith a major was rejected, is recorded in theexplanation twin per this repo's inline-decision rule.
Message shapes moved under that.
GridMsg::RowDeltabatchesrows: Vec<(u16, RowPayload)>;RehydrateBeginis a unit variant whosedimensions now reach a client through
AttachedorSize, so the generatorsemit an explicit
Sizefor a reader that has only the file.SpawnArgsgrewenv_base/tagsand takesOption<RequestedDims>; create+attach is onecreate_withround trip, which also closes the window where a rehydrate burstcould land before the recorder was subscribed. The row encoder is
encode_row_json(RowEncode { .. }), andtranscode::body_to_jsonno longertakes an encoding.
Docs. Every page stated felis facts that no longer exist —
WIRE_GENERATION,SCROLL_OP,picked_encoding,Conn::Ping,RowDeltaBatch, theControl/Logkinds. The pseudo-daemon contract gains the version-preface exchange itnever had to describe (negotiation used to ride the handshake) and records two
things the code always did but the page did not say:
Ops::Listis answeredcorrelated, and the roster row is minor-shaped. The shipped
skills/fcastskill is cascaded too.
Also corrects
felis-web→felis-web-componentin the browser-replayreferences, where the rename in
8171adahad not reached. (The partial work thisbranch started from had reverted that rename; restored.)
One thing documented rather than fixed: filed as felis#193
felis's
RowPayloadused to splice its structural JSON inline under ahuman-readable serializer; it is now
#[serde(transparent)]overVec<u8>.So a recorded row lands as a JSON array of byte values wrapping the row
codec's JSON text — correct and round-trippable, but 3.5x the size and one
decode step from readable. This is felis's serde shape reaching every consumer
of
transcode::body_to_json, not a felis-fcast choice: splicing it back herewould mean this repo defining part of the
msgschema, which its format seamexists to prevent.
So the reference states the shape, the explanation twin says why it is not
fixed here, and the skill grows a jq recipe that decodes a row. Filed upstream
as natsukium/felis#193, which also notes that felis's own round-trip test
asserts equality without asserting shape, so it passes while its name's
property does not hold.
Verified
All run in
nix develop, mirroring.forgejo/workflows/ci.yml1:1:cargo fmt --all --check— cleancargo clippy --all-targets --all-features -- -D warnings— cleancargo test --all-features— 20 passed, 0 failedcargo deny check— advisories, bans, licenses, sources okcargo run --example gen_hello_fcast/gen_scroll_fcast— both regenerateand re-parse
The load-bearing check is
tests/serve_roundtrip.rs: it bindsserveon asocket and drives felis-client-core's real connect/attach path against it —
preface,
Hello/Welcome, correlatedOps::List, attach and create — thendecodes the replayed
RowDeltaback through the packed wire row codec andcompares the cells. That exercises the whole adapted stack against the actual
client, not a mock.
Also swept: no reference to the old rev or to any removed felis name survives
outside the v1→v2 decision record that names them as retired; every relative
doc link resolves.
Left open
Imagereplay is untested. felis made image transfers canonical andstateful (
68fe54cf:Headernow namesNew/Frame,total_bytesandchunk
offsetgone).servereplays recordedImageframes verbatim andrecordstores them, so both follow the new shape for free — but no testcovers an image recording, and neither generator emits one. Tracked in
docs/backlog.mdalongside the--speedpacing andDetachgaps; not newto this change.
.fcastfiles itships are envelope v1, which no current reader accepts. Cross-repo, so it
cannot land here; noted in
docs/backlog.md.felis reset its wire-break baseline to 1.0 (felis f56f5529) and grew the schema to PROTOCOL_MINOR 10, so the pin this repo had frozen at 32775067 names a protocol that no longer exists. Every surface this tool touches moved at once, and none of them degrade: they are renames and removals. The negotiation surface is the break with the widest reach. The `Capabilities` bitfield and `Encoding` enum are gone — a connection is now a frozen 8-byte preface that agrees a major and an effective minor, and the wire carries protobuf alone. `record` therefore dials `connect(.., Offer::window(false))` instead of naming an encoding preference, and `serve` answers the preface with `DaemonPreface::select` and holds its `FrameWriter` at the negotiated minor, so every reply it writes passes the same send gate a real daemon's does. That removal reaches the `.fcast` header, which recorded `proto_version` (felis's retired `WIRE_GENERATION`) plus the negotiated capability names and bits. None of the three has a felis counterpart to carry forward, so the header now records `proto_major` and `proto_minor` — the effective minor, not this build's, since the bodies can only hold what both peers agreed on. Removing header fields bumps the envelope version per docs/reference/format.md "Versioning", so SUPPORTED_VERSION is 2. The message shapes moved under that: `GridMsg::RowDelta` batches `rows: Vec<(u16, RowPayload)>` where it carried one `row` + `packed_cells`; `RehydrateBegin` is a unit variant, its dimensions now reaching a client through `Attached` or `Size`, so the generators emit an explicit `Size` for a reader that has only the file. `SpawnArgs` grew `env_base`/`tags` and takes `Option<RequestedDims>`, and create+attach is one `create_with` round trip rather than spawn-then-attach — which also closes the window where a rehydrate burst could land before the recorder was subscribed. The row encoder is `encode_row_json(RowEncode { .. })`, and `transcode::body_to_json` no longer takes an encoding. `record`'s `Offer::window(false)` is deliberate: `Window` is the only mode felis streams a grid to, and a recording of anything less is not a recording of the session. Docs still state the retired facts and are cascaded separately. fmt, clippy -D warnings, 20 tests and cargo deny green; both sample generators regenerate.