fix(gpu,daemon): 1a review round 1 --- three P1s, and the version fallout
**A7 AND A8 WERE UNREACHABLE FROM THE REAL PRODUCER.** The intercept
branch sends a truncated `Key` and returns, and TextInput classification
sat below it --- but a modal prompt or a focused terminal is exactly
what makes `daemon_intercepts_keys` true, so the two contracts about
prompts and terminals were reachable only when neither was present. The
selection moves ABOVE the intercept return, where the producer sends the
same `TextInput` in every state and the daemon applies the modal
precedence, which is where it belongs: the frontend cannot see which
shadow is up.
Ordering against the branches below is safe by construction rather than
by luck --- `text_input_payload` returns `None` whenever a command
modifier is held, so Ctrl-V and command chords can never be shadowed.
**A pure `text_input_payload` test cannot catch this**, which is the
lesson worth keeping: the classifier was right the whole time and the
call site was wrong. The witness has to drive `intercept = true` and a
terminal.
**SINGLE-SCALAR PROVENANCE WAS PROMISED IN A COMMENT AND NOT
IMPLEMENTED.** §5 rules that a single-scalar commit is indistinguishable
from a keypress; the code only broke the chain for multi-scalar and
called a generic insert, so `this_command` went stale and no
`TypedEditRecord` was produced. Auto-pairing (Q#AP9) and every other
typed-edit consumer would have silently stopped recognizing GUI input
--- surfacing as "auto-pair stopped working in the GUI", far from its
cause. Now runs the real machinery: `rotate_command("buffer.self-insert")`
-> `typed_edit_arm(ch)` -> the one edit -> `typed_edit_finish` ->
`typed_edit_set_armed` -> `buffer.after-edit` -> clear, which is the
tail `dispatch_key` already runs.
**THE PRODUCER GATE WAS ONLY HALF THE WIRE CONTRACT.** The daemon
accepted `TextInput` from every installed session, so a peer negotiated
at v6-v23 --- compiled from this same crate, and postcard will happily
write the discriminant --- could mutate a buffer through a variant its
own session never declared. Now gated on the AUTHENTICATED session's
negotiated version.
**A4's structural half is implemented, not just its behaviour.**
`apply_keyboard` returns `()`, so `LifecycleRoute::Exit` is the sole
`EventOutcome::Exit` producer and the obsolete keyboard-exit channel is
gone rather than merely unused. The type survives, as ruled: one
producer is not one variant.
Also: `dispatch_text_input`'s rustdoc claimed a boolean return that its
signature does not have.
VERSION FALLOUT, SORTED RATHER THAN RENUMBERED.
Six deliberate tripwires took the conscious edit they exist to force
(protocol.rs, bottom-panel, discovery x2, statusline, and the vterm one
that was missing from my inventory). Two carried the version in their
NAME, so the name moved with the number rather than being left to lie.
Two ceiling assertions --- `!is_supported_protocol_version(24)` ---
now probe `PROTOCOL_VERSION + 1`, so they keep meaning "the set ends at
the current wire" instead of needing a hand-edit every bump.
`m4_6_handshake_accepts_v6_peer` was GENUINELY DEFECTIVE and is the one
real find: its name and the M4.6 contract say **v6 is the floor**, but
its body asserted `is_supported_protocol_version(PROTOCOL_VERSION)` ---
"the current wire accepts itself", a different and far weaker claim that
would have kept passing after v6 was dropped from the supported set,
which is the only regression it exists to catch. Anchored on literal 6.
The M10 pair needed no edit: they already use `PROTOCOL_VERSION`, and
they failed in the first sweep only because it predated the
`SUPPORTED_PROTOCOL_VERSIONS` fix.
`ADVERTISED_PROTOCOL_VERSION == 20` did not fire, as it must not.
Full `--workspace --no-fail-fast` sweep clean under an isolated TMPDIR.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
This commit is contained in:
parent
211241a437
commit
266bc6e4f3
|
|
@ -3162,7 +3162,7 @@ impl App {
|
|||
Route::Keyboard {
|
||||
action: KeyAction::Press,
|
||||
key,
|
||||
} => return self.apply_keyboard(key),
|
||||
} => self.apply_keyboard(key),
|
||||
Route::Pointer(PointerRoute::Moved { x, y }) => self.apply_cursor_moved(x, y),
|
||||
Route::Pointer(PointerRoute::Left(button_state)) => {
|
||||
self.apply_left_button(button_state);
|
||||
|
|
@ -3186,7 +3186,7 @@ impl App {
|
|||
/// Perform [`KeyAction::Press`]. The router has already
|
||||
/// discarded key-ups, so `key` is always a press.
|
||||
#[allow(clippy::too_many_lines)] // one linear key pipeline; splitting hides the order.
|
||||
fn apply_keyboard(&mut self, key: &KeyEvent) -> EventOutcome {
|
||||
fn apply_keyboard(&mut self, key: &KeyEvent) {
|
||||
// While the daemon is intercepting keystrokes — an active
|
||||
// incremental search (Q#SR5), or a minibuffer / pending
|
||||
// prefix — every key belongs to its handler, not the
|
||||
|
|
@ -3235,11 +3235,11 @@ impl App {
|
|||
{
|
||||
eprintln!("pmacs-gpu: send Escape failed: {e}");
|
||||
}
|
||||
return EventOutcome::Continue;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some((pkey, mut pmods)) = translate_key(&key.logical_key, self.modifiers) else {
|
||||
return EventOutcome::Continue;
|
||||
return;
|
||||
};
|
||||
|
||||
// AltGr / international text (audit F-004). winit reports
|
||||
|
|
@ -3275,13 +3275,57 @@ impl App {
|
|||
{
|
||||
eprintln!("pmacs-gpu: send_paste failed: {e}");
|
||||
}
|
||||
return EventOutcome::Continue;
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(client) = self.attach_client.as_ref() else {
|
||||
return EventOutcome::Continue;
|
||||
return;
|
||||
};
|
||||
|
||||
// A5 — multi-scalar text travels as ONE `TextInput`, if the
|
||||
// session can carry it.
|
||||
//
|
||||
// **This MUST precede the intercept branch below, and that
|
||||
// placement is the contract rather than a preference.** A
|
||||
// modal prompt or a focused terminal is exactly what makes
|
||||
// `daemon_intercepts_keys` true, so classifying after it would
|
||||
// leave A7 (prompts consume scalars in order) and A8 (terminals
|
||||
// take raw UTF-8) reachable only when neither a prompt nor a
|
||||
// terminal is present — which is to say, never. Sited here, the
|
||||
// producer sends the same `TextInput` in every state and the
|
||||
// daemon's `dispatch_text_input` applies §5's modal precedence,
|
||||
// which is where that decision belongs: the frontend cannot see
|
||||
// which shadow is up.
|
||||
//
|
||||
// Ordering against the branches below is safe by construction,
|
||||
// not by luck: `text_input_payload` returns `None` whenever a
|
||||
// command modifier is held, so the Ctrl-V paste and
|
||||
// command-chord paths can never be shadowed by it.
|
||||
//
|
||||
// **The version gate WITHHOLDS rather than degrades.** A `< 24`
|
||||
// daemon keeps exactly the behaviour it has — including today's
|
||||
// truncation to the first scalar — because the fallback is the
|
||||
// unchanged `Key` path below. No regression, not retroactive
|
||||
// correctness.
|
||||
if let Some(text) = text_input_payload(&key.logical_key, key.text.as_deref(), pmods)
|
||||
&& client.session_protocol_version() >= TEXT_INPUT_MIN_VERSION
|
||||
{
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.mark_cursor_stale_after_round_trip();
|
||||
}
|
||||
if debug_input() {
|
||||
eprintln!(
|
||||
"pmacs-gpu send_text_input: {} scalars",
|
||||
text.chars().count()
|
||||
);
|
||||
}
|
||||
let client = self.attach_client.as_ref().expect("client checked above");
|
||||
if let Err(e) = client.send_text_input(text) {
|
||||
eprintln!("pmacs-gpu: send_text_input failed: {e}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Intercept path: round-trip every key into the daemon's
|
||||
// active handler (search query / step / accept / cancel).
|
||||
if intercept {
|
||||
|
|
@ -3294,7 +3338,7 @@ impl App {
|
|||
if let Err(e) = client.send_key(pkey, pmods) {
|
||||
eprintln!("pmacs-gpu: send_key (intercepted) failed: {e}");
|
||||
}
|
||||
return EventOutcome::Continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// Idle: forward any command chord (Char/Enter/Tab with
|
||||
|
|
@ -3315,33 +3359,7 @@ impl App {
|
|||
if let Err(e) = client.send_key(pkey, pmods) {
|
||||
eprintln!("pmacs-gpu: send_key (command chord) failed: {e}");
|
||||
}
|
||||
return EventOutcome::Continue;
|
||||
}
|
||||
|
||||
// A5 — multi-scalar text travels as one `TextInput`, if the
|
||||
// session can carry it.
|
||||
//
|
||||
// Sited AFTER the command-chord and OS-paste branches and
|
||||
// BEFORE the optimistic path, which is the order §5's rules
|
||||
// describe: a chord is never text, and text must not be
|
||||
// optimistically applied one scalar at a time when the whole
|
||||
// point is that it is one edit.
|
||||
//
|
||||
// **The version gate withholds rather than degrades.** A `< 24`
|
||||
// daemon keeps exactly the behaviour it has — including today's
|
||||
// truncation to the first scalar — because the fallback below
|
||||
// is the unchanged `Key` path. That is the no-regression
|
||||
// promise, not retroactive correctness.
|
||||
if let Some(text) = text_input_payload(&key.logical_key, key.text.as_deref(), pmods)
|
||||
&& client.session_protocol_version() >= TEXT_INPUT_MIN_VERSION
|
||||
{
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.mark_cursor_stale_after_round_trip();
|
||||
}
|
||||
if let Err(e) = client.send_text_input(text) {
|
||||
eprintln!("pmacs-gpu: send_text_input failed: {e}");
|
||||
}
|
||||
return EventOutcome::Continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// Session B2 forwards cursor motion + plain text editing
|
||||
|
|
@ -3350,7 +3368,7 @@ impl App {
|
|||
// here and are withheld, leaving OS/WM shortcuts (Cmd-Q,
|
||||
// Cmd-C) to the platform.
|
||||
if !should_forward_key(pkey, pmods) {
|
||||
return EventOutcome::Continue;
|
||||
return;
|
||||
}
|
||||
|
||||
// Arc 1a Q#C6 — with the popup open, RET and TAB mean
|
||||
|
|
@ -3387,7 +3405,7 @@ impl App {
|
|||
{
|
||||
eprintln!("pmacs-gpu: send Viewport failed: {e}");
|
||||
}
|
||||
return EventOutcome::Continue;
|
||||
return;
|
||||
}
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
if state.defer_round_trip_key_if_needed(pkey, pmods) {
|
||||
|
|
@ -3397,7 +3415,7 @@ impl App {
|
|||
pending optimistic cursor"
|
||||
);
|
||||
}
|
||||
return EventOutcome::Continue;
|
||||
return;
|
||||
}
|
||||
state.mark_cursor_stale_after_round_trip();
|
||||
}
|
||||
|
|
@ -3407,7 +3425,6 @@ impl App {
|
|||
if let Err(e) = client.send_key(pkey, pmods) {
|
||||
eprintln!("pmacs-gpu: send_key failed: {e}");
|
||||
}
|
||||
EventOutcome::Continue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3448,23 +3465,19 @@ enum Route<'a> {
|
|||
|
||||
/// What the event loop must do once a family's body has run.
|
||||
///
|
||||
/// **Two producers, and they are not the same kind of thing.**
|
||||
/// `LifecycleRoute::Exit` is a native window close, which must always
|
||||
/// exit; `apply_keyboard` returns `Exit` for an idle Escape, which is a
|
||||
/// local quit. Returning the decision rather than taking an
|
||||
/// `&ActiveEventLoop` is what keeps every body reachable from a test:
|
||||
/// the crate has **exactly one** executable `event_loop.exit()`, in
|
||||
/// `window_event`.
|
||||
/// **Since A4, `LifecycleRoute::Exit` — a native window close — is the
|
||||
/// SOLE producer.** `apply_keyboard` used to be the second, for the
|
||||
/// idle-Escape local quit; A4 deleted that branch and with it the
|
||||
/// keyboard body's need to return anything, so it returns `()` and the
|
||||
/// obsolete channel is gone rather than merely unused.
|
||||
///
|
||||
/// **Stage 1a's A4 removes the KEYBOARD producer only** — an idle
|
||||
/// Escape must reach the daemon and never exit — leaving **one** `Exit`
|
||||
/// producer, the native close.
|
||||
///
|
||||
/// **One producer is not one variant.** This type survives A4 because
|
||||
/// **One producer is not one variant.** The type stays because
|
||||
/// `dispatch_window_event` must still distinguish `Continue` from
|
||||
/// `Exit` on every event it handles: nearly all of them must not exit,
|
||||
/// and the close must. What A4 changes is `apply_keyboard`'s signature,
|
||||
/// not this type.
|
||||
/// `Exit` on every event it handles: nearly all must not exit, and the
|
||||
/// close must. Returning the decision rather than taking an
|
||||
/// `&ActiveEventLoop` is also what keeps the bodies reachable from a
|
||||
/// test — the crate has exactly one executable `event_loop.exit()`, in
|
||||
/// `window_event`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum EventOutcome {
|
||||
Continue,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ use crate::protocol::{
|
|||
InitialTarget, InitialTargetResult, InstanceCapabilities, InstanceIdentity, InstanceMessage,
|
||||
InstanceSignal, MAX_INITIAL_TARGET_ERROR_BYTES, MAX_INITIAL_TARGET_PATH_BYTES,
|
||||
PANEL_MIN_VERSION, PointerKind, SelectionSnapshot, SessionBootstrapRequest,
|
||||
TEXT_INPUT_MAX_BYTES,
|
||||
TEXT_INPUT_MAX_BYTES, TEXT_INPUT_MIN_VERSION,
|
||||
};
|
||||
use crate::socket_path::{SocketPathError, ensure_runtime_subdir};
|
||||
use crate::transport::{read_message, write_message};
|
||||
|
|
@ -2536,6 +2536,25 @@ fn handle_dispatcher_event(
|
|||
// worse than a refused one. An empty payload is
|
||||
// dropped too — it would be an edit that edits
|
||||
// nothing, and would still cost an undo unit.
|
||||
// **The producer gate is only half the contract.**
|
||||
// A frontend that negotiated v6–v23 can still encode
|
||||
// this variant — it is compiled from the same crate,
|
||||
// and postcard will happily write the discriminant —
|
||||
// so a peer that never declared v24 could otherwise
|
||||
// mutate the buffer through a variant its own
|
||||
// session does not include. Gate on the
|
||||
// AUTHENTICATED session's negotiated version, not on
|
||||
// the payload and not on what the daemon supports.
|
||||
let peer_declared_text_input = session_registry
|
||||
.session_state(source)
|
||||
.is_some_and(|s| s.negotiated_protocol_version >= TEXT_INPUT_MIN_VERSION);
|
||||
if !peer_declared_text_input {
|
||||
eprintln!(
|
||||
"pmacs: dropping TextInput from {source:?}, which negotiated \
|
||||
below v{TEXT_INPUT_MIN_VERSION}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1906,9 +1906,11 @@ impl EditorState {
|
|||
/// a time, in order** (A7) — order is the contract, since a prompt
|
||||
/// accumulates a query.
|
||||
///
|
||||
/// Returns `true` when the text was consumed by a shadow or a
|
||||
/// terminal, `false` when the caller should treat it as an ordinary
|
||||
/// document edit — the same convention as [`Self::dispatch_paste`].
|
||||
/// Returns nothing, unlike [`Self::dispatch_paste`], and the
|
||||
/// difference is real rather than stylistic: §5's precedence is
|
||||
/// **total** — shadow, terminal, or document, every state routes
|
||||
/// somewhere — so there is no "unhandled" case for a caller to fall
|
||||
/// back on.
|
||||
pub fn dispatch_text_input(&mut self, frontend_id: FrontendId, text: &str) {
|
||||
self.core.borrow_mut().active_frontend = frontend_id;
|
||||
|
||||
|
|
@ -1924,21 +1926,57 @@ impl EditorState {
|
|||
return;
|
||||
}
|
||||
|
||||
// A6 — the ordinary document path: ONE edit.
|
||||
self.with_after_edit_check(|state| {
|
||||
let mut core = state.core.borrow_mut();
|
||||
// Provenance follows §5. A single-scalar commit is
|
||||
// indistinguishable from a keypress and keeps today's
|
||||
// one-codepoint typed provenance; a multi-scalar commit is
|
||||
// not a command and breaks the chain, exactly as a paste
|
||||
// does.
|
||||
if text.chars().count() > 1 {
|
||||
core.break_command_chain(frontend_id);
|
||||
// §5's provenance split. A SINGLE-scalar commit is
|
||||
// indistinguishable from a keypress, so it must be
|
||||
// indistinguishable downstream too: it rotates to
|
||||
// `buffer.self-insert` and produces the same one-codepoint
|
||||
// typed-edit record a keystroke does. Without that, auto-pairing
|
||||
// (Q#AP9) and every other typed-edit consumer silently stop
|
||||
// recognizing GUI input, and `this_command` goes stale — a
|
||||
// regression that shows up as "auto-pair stopped working in the
|
||||
// GUI", far from its cause.
|
||||
//
|
||||
// A MULTI-scalar commit is not a keystroke: it breaks the
|
||||
// command chain and creates no typed provenance, exactly as a
|
||||
// paste does.
|
||||
let single = {
|
||||
let mut chars = text.chars();
|
||||
match (chars.next(), chars.next()) {
|
||||
(Some(ch), None) => Some(ch),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
let pre_revision = self.active_buffer_revision();
|
||||
{
|
||||
let mut core = self.core.borrow_mut();
|
||||
match single {
|
||||
Some(ch) => {
|
||||
core.rotate_command(frontend_id, "buffer.self-insert");
|
||||
core.typed_edit_arm(frontend_id, ch);
|
||||
}
|
||||
None => core.break_command_chain(frontend_id),
|
||||
}
|
||||
// A6 — ONE edit, whichever branch armed it.
|
||||
if let Err(e) = core.insert_text_input(text) {
|
||||
eprintln!("pmacs: text input failed: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The dispatch tail `dispatch_key` runs, for the same reason: a
|
||||
// typed-edit record is only meaningful to a hook that can see
|
||||
// it, so it is armed across the fan-out and cleared after.
|
||||
let typed_edit = self.core.borrow_mut().typed_edit_finish(frontend_id);
|
||||
if pre_revision != self.active_buffer_revision() {
|
||||
if let Some(record) = typed_edit {
|
||||
self.core
|
||||
.borrow_mut()
|
||||
.typed_edit_set_armed(frontend_id, record);
|
||||
}
|
||||
self.lua_host
|
||||
.run_hook("buffer.after-edit", mlua::MultiValue::new());
|
||||
self.core.borrow_mut().typed_edit_clear_armed();
|
||||
}
|
||||
}
|
||||
|
||||
/// Feed `text` to whichever modal shadow owns input, one scalar at a
|
||||
|
|
|
|||
|
|
@ -1683,7 +1683,7 @@ mod tests {
|
|||
// --- M5.5a handshake & postcard round-trips ---
|
||||
|
||||
#[test]
|
||||
fn protocol_version_is_twenty_three_for_minibuffer_prompt_rows() {
|
||||
fn protocol_version_is_twenty_four_for_text_input() {
|
||||
// Pin the value: T M10.5 bumped 1→2 (v1.0 wire: CrdtOp /
|
||||
// PresenceUpdate). T M11.1 bumped 2→3 (v1.1 wire: the
|
||||
// SemanticFrame family + FrontendEvent::Viewport). T M11.6
|
||||
|
|
@ -1740,7 +1740,11 @@ mod tests {
|
|||
// and gating the wider form would have left them with no
|
||||
// minibuffer at all. `MinibufferPrompt` is therefore frozen and
|
||||
// pinned by literal bytes below.
|
||||
assert_eq!(PROTOCOL_VERSION, 23);
|
||||
//
|
||||
// v24 is `FrontendEvent::TextInput` (GUI arc Stage 1a) — an
|
||||
// APPENDED variant, which is why the freeze above survives it
|
||||
// untouched: nothing in `MinibufferPrompt`'s encoding moved.
|
||||
assert_eq!(PROTOCOL_VERSION, 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1817,18 +1821,19 @@ mod tests {
|
|||
// (`CompletionPopup`), v16 (`ThemeFacts`), v17 (`FontFacts`),
|
||||
// v18 (`StatuslineSegments`), v19 (the vterm terminal family),
|
||||
// v20 (semantic initial-target bootstrap), v21 (the bottom
|
||||
// panel band), v22 (`LineWrapFacts`), and v23
|
||||
// (`MinibufferPromptRows`) all interoperate.
|
||||
for accepted in 6..=23 {
|
||||
// panel band), v22 (`LineWrapFacts`), v23
|
||||
// (`MinibufferPromptRows`), and v24 (`TextInput`, GUI arc Stage
|
||||
// 1a) all interoperate.
|
||||
for accepted in 6..=24 {
|
||||
assert!(
|
||||
is_supported_protocol_version(accepted),
|
||||
"v{accepted} must be accepted"
|
||||
);
|
||||
}
|
||||
for rejected in [0, 1, 2, 3, 4, 5, 24, u32::MAX] {
|
||||
for rejected in [0, 1, 2, 3, 4, 5, 25, u32::MAX] {
|
||||
assert!(
|
||||
!is_supported_protocol_version(rejected),
|
||||
"v{rejected} must be rejected by a v23 binary"
|
||||
"v{rejected} must be rejected by a v24 binary"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2738,9 +2743,20 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn m4_6_handshake_accepts_v6_peer() {
|
||||
// ANCHORED ON THE LITERAL 6, deliberately. The body used to
|
||||
// assert `is_supported_protocol_version(PROTOCOL_VERSION)` —
|
||||
// "the current wire accepts itself" — which is a different and
|
||||
// much weaker claim than the name and the M4.6 contract make:
|
||||
// **v6 is the FLOOR**, the oldest peer the handshake still
|
||||
// admits, and it must keep being accepted no matter how far the
|
||||
// ceiling moves. Written against the moving constant, the test
|
||||
// would have gone on passing after v6 was dropped from the
|
||||
// supported set, which is the only regression it exists to
|
||||
// catch. Found when the v24 bump made it fail for the unrelated
|
||||
// reason that `SUPPORTED_PROTOCOL_VERSIONS` had not been widened.
|
||||
assert!(
|
||||
is_supported_protocol_version(PROTOCOL_VERSION),
|
||||
"the current wire version must accept itself"
|
||||
is_supported_protocol_version(6),
|
||||
"v6 is the floor and must stay accepted"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -337,9 +337,10 @@ fn one_daemon_serves_a_v21_panel_session_and_a_shipped_v20_client() {
|
|||
#[test]
|
||||
fn the_baseline_stays_and_the_counter_offer_activates() {
|
||||
// A deliberate tripwire: bumping the wire must be a conscious edit
|
||||
// here, not a silent one. v23 is `MinibufferPromptRows` (Discovery
|
||||
// Stage 2); v22 was `LineWrapFacts` (long-lines Stage 3).
|
||||
assert_eq!(PROTOCOL_VERSION, 23);
|
||||
// here, not a silent one. v24 is `TextInput` (GUI arc Stage 1a);
|
||||
// v23 was `MinibufferPromptRows` (Discovery Stage 2); v22 was
|
||||
// `LineWrapFacts` (long-lines Stage 3).
|
||||
assert_eq!(PROTOCOL_VERSION, 24);
|
||||
assert_eq!(
|
||||
ADVERTISED_PROTOCOL_VERSION, 20,
|
||||
"moving this is the incompatible act the mechanism exists to avoid"
|
||||
|
|
|
|||
|
|
@ -67,10 +67,10 @@ use common::daemon::{TestDaemon, build_default_caps};
|
|||
/// server-first, so moving it locks out every already-shipped frontend
|
||||
/// before it can counter-offer. An additive family never needs it.
|
||||
#[test]
|
||||
fn the_wire_is_v23_and_the_advertised_baseline_is_unmoved() {
|
||||
fn the_wire_is_v24_and_the_advertised_baseline_is_unmoved() {
|
||||
assert_eq!(
|
||||
PROTOCOL_VERSION, 23,
|
||||
"v23 is MinibufferPromptRows (Discovery Stage 2)"
|
||||
PROTOCOL_VERSION, 24,
|
||||
"v24 is TextInput (GUI arc Stage 1a); v23 was MinibufferPromptRows"
|
||||
);
|
||||
assert_eq!(
|
||||
ADVERTISED_PROTOCOL_VERSION, 20,
|
||||
|
|
@ -85,7 +85,10 @@ fn the_wire_is_v23_and_the_advertised_baseline_is_unmoved() {
|
|||
"v{version} must still be supported"
|
||||
);
|
||||
}
|
||||
assert!(!is_supported_protocol_version(24));
|
||||
// The ceiling: the supported set ENDS at the current wire, which
|
||||
// is what makes an accidentally-widened set a failure rather than
|
||||
// a silent pass. Probes one PAST the top, so it moves with it.
|
||||
assert!(!is_supported_protocol_version(PROTOCOL_VERSION + 1));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -612,7 +615,7 @@ fn one_daemon_serves_a_v23_rows_session_and_a_frozen_v22_session() {
|
|||
// rather than after the interesting half has already passed.
|
||||
let (mut legacy, _legacy_fid) = attach_semantic(&daemon, 22);
|
||||
let (mut current, current_fid) = attach_semantic(&daemon, PROTOCOL_VERSION);
|
||||
assert_eq!(PROTOCOL_VERSION, 23);
|
||||
assert_eq!(PROTOCOL_VERSION, 24);
|
||||
|
||||
// Open the real `M-x` through the real key path, then narrow to the
|
||||
// probe command by typing it — the candidate window is ten rows out
|
||||
|
|
|
|||
|
|
@ -804,11 +804,11 @@ fn a13_17_26_protocol_semantic_init_late_join_and_version_cost() {
|
|||
// three lines on purpose. The ceiling assertion is the load-bearing
|
||||
// one — it says the supported set ENDS here, which is what makes an
|
||||
// accidentally-widened set a failure rather than a silent pass.
|
||||
assert_eq!(PROTOCOL_VERSION, 23);
|
||||
for version in 6..=23 {
|
||||
assert_eq!(PROTOCOL_VERSION, 24);
|
||||
for version in 6..=24 {
|
||||
assert!(is_supported_protocol_version(version));
|
||||
}
|
||||
assert!(!is_supported_protocol_version(24));
|
||||
assert!(!is_supported_protocol_version(PROTOCOL_VERSION + 1));
|
||||
let sample = InstanceMessage::StatuslineSegments {
|
||||
buffer_id: BufferId::from_raw(9),
|
||||
left: vec![StatuslineSegment {
|
||||
|
|
|
|||
|
|
@ -888,10 +888,10 @@ fn terminal_mode_keeps_reporting_presence_so_peers_drop_the_stale_caret() {
|
|||
panic!("timed out waiting for {what}");
|
||||
}
|
||||
|
||||
// Tripwire: a wire bump must be a conscious edit here. v23 is
|
||||
// `MinibufferPromptRows` (Discovery Stage 2); v22 was
|
||||
// `LineWrapFacts` (long-lines Stage 3).
|
||||
assert_eq!(PROTOCOL_VERSION, 23);
|
||||
// Tripwire: a wire bump must be a conscious edit here. v24 is
|
||||
// `TextInput` (GUI arc Stage 1a); v23 was `MinibufferPromptRows`
|
||||
// (Discovery Stage 2); v22 was `LineWrapFacts` (long-lines Stage 3).
|
||||
assert_eq!(PROTOCOL_VERSION, 24);
|
||||
let daemon = common::daemon::TestDaemon::spawn_with_env_and_init(
|
||||
&[
|
||||
("PMACS_INSTANCE_SEMANTIC_RENDER", "1"),
|
||||
|
|
|
|||
Loading…
Reference in New Issue