grid: give structural session JSON a named format and an owner #341
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
Due date
No due date set.
Dependencies
No dependencies set
Reference
natsukium/felis!341
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-336"
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?
Closes #336.
Structural session JSON now has an owner and a version:
felis_grid::json_v1is a named format ({"felis_json": 1, "kind", "msg"}) with dedicated DTOs for Grid, Image, Conn, Session and Input, explicit conversions to and from the domain types (a domain rename fails to compile rather than changing the JSON), structural row cells with their own DTO tree, a generated JSON Schema (crates/felis-grid/schemas/felis-json-v1.schema.json, staleness-tested, integer bounds and the OSC 66 fraction invariant encoded, goldens validated against it), golden fixtures including aRowDeltawith structural rows, and a compatibility policy stricter than the wire's because a.fcastfile or a deployed browser has no effective-minor gate. The bridge-only families (Ops, Region, Notify, Push, Search) move intofelis-clibehind the CLI's ownv: 1contract, byte-identical;felis-grid::transcodeand the row JSON helpers are removed so the crate has one JSON spelling of a row.Decision: the last comment on #336 (six rounds of design review; Option 2 because
felis-web-gatewayandfelis-fcastboth consume the shape today and fcast persists it). Code review: pi luna (timestamp normalization) then pi sol, five rounds (row cells DTO, checked bitmaps, reader/schema parity, schema soundness, feature gating): PASS.Satellite follow-ups (their repos): adopt the envelope via
json_v1::{encode, decode}, drop their own kind-name mapping, read rows asRowCellsJson, emit lowercase session ids, and take the daemon's build fromidentity.`felis-grid::transcode` was described as the shared JSON engine for the consumers that re-expose or record a daemon session, but what it published was the direct serde rendering of felis's own Rust enums. Two out-of-repo consumers already depend on it by git rev, so a variant or field rename there is a silent break in a deployed browser view or in `.fcast` files nobody can re-record, and there was no version, no schema, and no validation that was not inherited from implementation types. `felis_grid::json_v1` replaces it with a named format. Dedicated DTOs for the five families a session mirror needs mean a domain rename fails the conversion to compile; an in-band `{"felis_json": 1, "kind", "msg"}` envelope lets a reader refuse a version it does not know before it looks at the payload; a generated schema and golden frames pin what a writer emits, the `RowDelta` rows included, which is where #193's structural row lands without blessing the rest of the serde shape. The DTOs reach down to a dirty row's cells, the format's largest and most-read payload, so the row codec now owns the packed bytes alone and this format owns their JSON spelling; the crate had two spellings of a row while `wire`'s JSON view remained, which is the drift the whole change is against. Admission is one rule, stated twice. A conversion refuses every input the row codec would refuse on the way back in, so a replay cannot get a frame past the envelope and then kill the consuming client's connection with it: reserved bitmap bits, non-printable ASCII, a sized cell outside the row, a zero handle. The schema states the same bounds wherever a validator can carry them, integer widths included, since `format` is an annotation a validator may ignore; the three structural checks it cannot carry are named in `ipc.md` rather than left for a consumer to discover as a decode failure it was told could not happen. The compatibility rule is stricter than the wire's additive minor (REQ-104b) because a recording on disk and a separately deployed browser have no effective-minor handshake: inside a version only an optional field readers ignore may be added, and everything wider bumps the version. The alternative, leaving the conversion an internal helper so each satellite owns its own DTOs, was rejected in the issue: it is the drift a shared engine exists to prevent. Closes #336. Assisted-by: Claude Code