tui: mark friends invite-stale after an F8 prekey rotation #197

Owner

Makes the F8 hard cut legible instead of leaving the next send looking delivered.

What changed (src/node.rs, src/tui.rs, tests/tui_frames.rs, docs/TUI.md):

  • rotate_prekey() now snapshots the roster into $ONIONWIRE_HOME/invite-stale (header + one hex identity pubkey per line, atomic write). This is derived local state — no src/store.rs schema change, no wire field, no new crate. An install that never pressed F8 has no file and marks nobody. Node::invite_stale(pk) / Node::invite_stale_friends() answer "was this friend last bootstrapped before the current prekey existed?".
  • Node::send and Node::send_once fail closed with invite stale for a pre-rotation friend. Previously the ratchet re-bootstrapped silently from the peer's stored prekey, the frame encoded and looked sent, and the peer could not decrypt it.
  • The roster gets its own marker: invite stale, with its own colour token (violet; stale keeps meaning "the last dial did not answer"). The alert row above the panes carries invite stale — F2 to share your new invite, they F3-paste it, recomputed each draw, in the same place as fingerprint_mismatch_banner.
  • add_friend_payload (F3 paste) removes the pubkey from the set, which clears the row marker and unblocks sends.
  • docs/TUI.md documents the marker.

This does not add in-band revocation or a soft/push prekey rotation — docs/THREAT_MODEL.md's hard-cut wording stays exactly as it is, and no wire field is added.

Evidence

  • cargo test --locked --target x86_64-unknown-linux-gnu: 38 suites, 577 passed / 0 failed / 4 ignored (network tests).
  • cargo clippy --locked --all-targets --target x86_64-unknown-linux-gnu -- -D warnings: clean.
  • New tests: rotating_the_prekey_marks_existing_friends_invite_stale, pasting_a_new_invite_clears_the_invite_stale_marker (node unit tests, red before the change), an_invite_stale_friend_is_marked_apart_from_a_dial_stale_one, the_invite_stale_hint_renders_above_the_panes_at_80_columns (draws the real draw_main_chrome at 80x24; the rendered row is │ alice invite stale│, the hint row is 62 cells).
  • git diff --stat: docs/TUI.md, src/node.rs, src/tui.rs, tests/tui_frames.rs — no src/store.rs.

Stacking: base is the fil padding branch; both cards edit src/node.rs. Merge order is #196 (fil padding) first, then this PR.

Makes the F8 hard cut legible instead of leaving the next send looking delivered. **What changed** (`src/node.rs`, `src/tui.rs`, `tests/tui_frames.rs`, `docs/TUI.md`): - `rotate_prekey()` now snapshots the roster into `$ONIONWIRE_HOME/invite-stale` (header + one hex identity pubkey per line, atomic write). This is **derived local state** — no `src/store.rs` schema change, no wire field, no new crate. An install that never pressed F8 has no file and marks nobody. `Node::invite_stale(pk)` / `Node::invite_stale_friends()` answer "was this friend last bootstrapped before the current prekey existed?". - `Node::send` and `Node::send_once` fail closed with `invite stale` for a pre-rotation friend. Previously the ratchet re-bootstrapped silently from the peer's stored prekey, the frame encoded and looked sent, and the peer could not decrypt it. - The roster gets its own marker: ` invite stale`, with its own colour token (violet; `stale` keeps meaning "the last dial did not answer"). The alert row above the panes carries `invite stale — F2 to share your new invite, they F3-paste it`, recomputed each draw, in the same place as `fingerprint_mismatch_banner`. - `add_friend_payload` (F3 paste) removes the pubkey from the set, which clears the row marker and unblocks sends. - `docs/TUI.md` documents the marker. **This does not add in-band revocation or a soft/push prekey rotation** — `docs/THREAT_MODEL.md`'s hard-cut wording stays exactly as it is, and no wire field is added. **Evidence** - `cargo test --locked --target x86_64-unknown-linux-gnu`: 38 suites, **577 passed / 0 failed / 4 ignored** (network tests). - `cargo clippy --locked --all-targets --target x86_64-unknown-linux-gnu -- -D warnings`: clean. - New tests: `rotating_the_prekey_marks_existing_friends_invite_stale`, `pasting_a_new_invite_clears_the_invite_stale_marker` (node unit tests, red before the change), `an_invite_stale_friend_is_marked_apart_from_a_dial_stale_one`, `the_invite_stale_hint_renders_above_the_panes_at_80_columns` (draws the real `draw_main_chrome` at 80x24; the rendered row is `│ alice invite stale│`, the hint row is 62 cells). - `git diff --stat`: `docs/TUI.md`, `src/node.rs`, `src/tui.rs`, `tests/tui_frames.rs` — no `src/store.rs`. **Stacking:** base is the `fil` padding branch; both cards edit `src/node.rs`. Merge order is #196 (`fil` padding) first, then this PR.
tui: mark friends invite-stale after an F8 prekey rotation
All checks were successful
ci / test (pull_request) Successful in 21m19s
ci / sdk (pull_request) Successful in 2m7s
9b53632859
F8 replaces the Noise static prekey and the ML-KEM key together and clears
every stored ratchet, but nothing recorded the cut: a friend on the roster
still held an invite with the old prekey, the next inbound frame could not be
decrypted, our own outbound re-bootstrapped an epoch they did not have, and the
send looked delivered. The only fix -- ask them to paste the new invite -- was
never said.

- `rotate_prekey` snapshots the roster into `$ONIONWIRE_HOME/invite-stale`
  (derived local state; no store schema change, no wire field, no new crate).
  `Node::invite_stale(pk)` / `invite_stale_friends()` answer the question, and
  an install that never rotated has no file and nothing marked.
- `Node::send` and `Node::send_once` fail closed with `invite stale` for a
  pre-rotation friend instead of silently re-bootstrapping past the cut.
- The roster gains its own `invite stale` word and colour token (violet; `stale`
  keeps meaning "the last dial did not answer"), and the alert row above the
  panes carries `invite stale -- F2 to share your new invite, they F3-paste it`,
  recomputed every draw so F3-pasting an invite for that friend clears it.
- `add_friend_payload` removes the pubkey from the set, which is the test the
  card asks for; docs/TUI.md documents the marker.

This makes the existing hard cut legible. It does not add in-band revocation or
a soft rotation; the threat model's hard-cut wording stays as it is.
sirius merged commit d205e04d25 into onionwire/t_7a5e3d1d-pad-fil-chunks-to-length-buckets-and-doc 2026-10-29 02:09:53 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sirius/onionwire!197
No description provided.