sync: print probe endpoint versions

This commit is contained in:
Brad Stein 2026-05-01 16:06:52 -03:00
parent b948994811
commit 7a8150b2db
9 changed files with 131 additions and 13 deletions

View File

@ -174,5 +174,6 @@ Context: the mirrored browser probe finally reproduced the real failure class on
- Replaying the 0.16.21 artifact after 0.16.22 analyzer hardening changes the verdict from false `catastrophic_failure` to `gross_failure`: p95 `273.8 ms`, median `-188.4 ms`, 7 paired coded pulses. The raw activity-start delta (`-3620.7 ms`) is still printed, but it is ignored for verdict/calibration because it disagrees with coded pairs by `3432.3 ms`; unpaired video/audio onsets are printed for triage.
- 0.16.22 live mirrored run still failed with p95 `433.7 ms`, median `-359.4 ms`, and 5 paired coded pulses. Client telemetry showed camera uplink `latest_age_ms` repeatedly around `300-350 ms`, matching the measured skew; patch 0.16.23 to make video queues latest-only instead of draining stale-but-under-budget backlog.
- 0.16.23 local validation passed for fresh-queue behavior, uplink/probe freshness contracts, sync analyzer tests, client/server binary checks, and whitespace checks.
- 0.16.23 live mirrored run improved to p95 `215.2 ms`, median `+142.2 ms`, 13 paired coded pulses, and raw activity alignment within `6.6 ms` of coded pairs. Patch 0.16.24 makes the probe print local client and remote server versions before capture so every run records what was actually tested.
- [ ] Re-run the mirrored browser probe after the pre-start false-positive fix.
- [ ] Run Google Meet manual validation.

6
Cargo.lock generated
View File

@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lesavka_client"
version = "0.16.23"
version = "0.16.24"
dependencies = [
"anyhow",
"async-stream",
@ -1686,7 +1686,7 @@ dependencies = [
[[package]]
name = "lesavka_common"
version = "0.16.23"
version = "0.16.24"
dependencies = [
"anyhow",
"base64",
@ -1698,7 +1698,7 @@ dependencies = [
[[package]]
name = "lesavka_server"
version = "0.16.23"
version = "0.16.24"
dependencies = [
"anyhow",
"base64",

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package]
name = "lesavka_client"
version = "0.16.23"
version = "0.16.24"
edition = "2024"
[dependencies]

View File

@ -1,10 +1,10 @@
use anyhow::{Context, Result, bail};
#[cfg(not(coverage))]
use lesavka_common::lesavka::Empty;
use lesavka_common::lesavka::{
CapturePowerCommand, SetCapturePowerRequest, relay_client::RelayClient,
CapturePowerCommand, HandshakeSet, SetCapturePowerRequest, relay_client::RelayClient,
};
#[cfg(not(coverage))]
use lesavka_common::lesavka::{Empty, handshake_client::HandshakeClient};
#[cfg(not(coverage))]
use tonic::Request;
use tonic::transport::Channel;
@ -13,6 +13,7 @@ use lesavka_client::relay_transport;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CommandKind {
Status,
Version,
Auto,
On,
Off,
@ -27,6 +28,7 @@ impl CommandKind {
fn parse(value: &str) -> Option<Self> {
match value {
"status" | "get" => Some(Self::Status),
"version" | "versions" => Some(Self::Version),
"auto" => Some(Self::Auto),
"on" | "force-on" => Some(Self::On),
"off" | "force-off" => Some(Self::Off),
@ -52,7 +54,7 @@ enum ParseOutcome {
}
fn usage() -> &'static str {
"Usage: lesavka-relayctl [--server http://HOST:50051] <status|auto|on|off|recover-usb|recover-uac|recover-uvc|reset-usb>"
"Usage: lesavka-relayctl [--server http://HOST:50051] <status|version|auto|on|off|recover-usb|recover-uac|recover-uvc|reset-usb>"
}
fn parse_args_outcome_from<I, S>(args: I) -> Result<ParseOutcome>
@ -111,6 +113,7 @@ fn parse_args() -> Result<ParseOutcome> {
fn capture_power_request(command: CommandKind) -> Option<SetCapturePowerRequest> {
let (enabled, command) = match command {
CommandKind::Status
| CommandKind::Version
| CommandKind::RecoverUsb
| CommandKind::RecoverUac
| CommandKind::RecoverUvc
@ -136,6 +139,21 @@ async fn connect(server_addr: &str) -> Result<RelayClient<Channel>> {
Ok(RelayClient::new(channel))
}
#[cfg(not(coverage))]
async fn get_server_capabilities(server_addr: &str) -> Result<HandshakeSet> {
let channel = relay_transport::endpoint(server_addr)?
.tcp_nodelay(true)
.connect()
.await
.with_context(|| format!("connecting to handshake at {server_addr}"))?;
let mut client = HandshakeClient::new(channel);
Ok(client
.get_capabilities(Request::new(Empty {}))
.await
.context("querying server capabilities")?
.into_inner())
}
#[cfg(coverage)]
async fn connect(server_addr: &str) -> Result<RelayClient<Channel>> {
let channel = relay_transport::endpoint(server_addr)?
@ -154,6 +172,20 @@ fn print_state(state: lesavka_common::lesavka::CapturePowerState) {
println!("detail={}", state.detail);
}
fn print_versions(server_addr: &str, caps: &HandshakeSet) {
let server_version = if caps.server_version.is_empty() {
"unknown"
} else {
caps.server_version.as_str()
};
println!("client_version={}", lesavka_client::VERSION);
println!("client_full_version={}", lesavka_client::FULL_VERSION);
println!("server_addr={server_addr}");
println!("server_version={server_version}");
println!("server_camera_output={}", caps.camera_output);
println!("server_camera_codec={}", caps.camera_codec);
}
#[cfg(not(coverage))]
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
@ -164,6 +196,13 @@ async fn main() -> Result<()> {
return Ok(());
}
};
if config.command == CommandKind::Version {
let caps = get_server_capabilities(config.server.as_str()).await?;
print_versions(config.server.as_str(), &caps);
return Ok(());
}
let mut client = connect(config.server.as_str()).await?;
if let Some(request) = capture_power_request(config.command) {
@ -172,6 +211,7 @@ async fn main() -> Result<()> {
CommandKind::On => "forcing capture power on",
CommandKind::Off => "forcing capture power off",
CommandKind::Status
| CommandKind::Version
| CommandKind::RecoverUsb
| CommandKind::RecoverUac
| CommandKind::RecoverUvc
@ -228,7 +268,9 @@ async fn main() -> Result<()> {
println!("ok={}", reply.ok);
return Ok(());
}
CommandKind::Auto | CommandKind::On | CommandKind::Off => unreachable!(),
CommandKind::Version | CommandKind::Auto | CommandKind::On | CommandKind::Off => {
unreachable!()
}
};
print_state(reply);
@ -253,6 +295,8 @@ mod tests {
fn command_aliases_parse_to_stable_actions() {
assert_eq!(CommandKind::parse("status"), Some(CommandKind::Status));
assert_eq!(CommandKind::parse("get"), Some(CommandKind::Status));
assert_eq!(CommandKind::parse("version"), Some(CommandKind::Version));
assert_eq!(CommandKind::parse("versions"), Some(CommandKind::Version));
assert_eq!(CommandKind::parse("force-on"), Some(CommandKind::On));
assert_eq!(CommandKind::parse("force-off"), Some(CommandKind::Off));
assert_eq!(
@ -342,6 +386,7 @@ mod tests {
assert_eq!(off.command, CapturePowerCommand::ForceOff as i32);
assert!(capture_power_request(CommandKind::Status).is_none());
assert!(capture_power_request(CommandKind::Version).is_none());
assert!(capture_power_request(CommandKind::RecoverUsb).is_none());
assert!(capture_power_request(CommandKind::RecoverUac).is_none());
assert!(capture_power_request(CommandKind::RecoverUvc).is_none());

View File

@ -1,6 +1,6 @@
[package]
name = "lesavka_common"
version = "0.16.23"
version = "0.16.24"
edition = "2024"
build = "build.rs"

View File

@ -190,6 +190,37 @@ systemctl is-active lesavka-server lesavka-uvc lesavka-core >/dev/null
REMOTE_PREFLIGHT
}
print_lesavka_versions() {
echo "==> Lesavka versions under test"
if [[ ! -x "${REPO_ROOT}/target/debug/lesavka-relayctl" ]]; then
(cd "${REPO_ROOT}" && cargo build -p lesavka_client --bin lesavka-relayctl >/dev/null)
fi
local version_output
if ! version_output="$(
LESAVKA_TLS_DOMAIN="${LESAVKA_TLS_DOMAIN}" \
"${REPO_ROOT}/target/debug/lesavka-relayctl" \
--server "${RESOLVED_LESAVKA_SERVER_ADDR}" \
version 2>&1
)"; then
echo "failed to query Lesavka versions through ${RESOLVED_LESAVKA_SERVER_ADDR}" >&2
echo "${version_output}" >&2
return 1
fi
if ! grep -q "^client_version=" <<<"${version_output}"; then
echo "Lesavka version query did not report client_version=; refusing to run an unattributed probe" >&2
echo "${version_output}" >&2
return 1
fi
if ! grep -q "^server_version=" <<<"${version_output}"; then
echo "Lesavka version query did not report server_version=; refusing to run an unattributed probe" >&2
echo "${version_output}" >&2
return 1
fi
while IFS= read -r line; do
[[ -n "${line}" ]] && echo "${line}"
done <<<"${version_output}"
}
if [[ "${LOCAL_AUDIO_SANITY}" != "0" ]]; then
echo "==> verifying local speaker-to-mic sanity before upstream sync run"
"${SCRIPT_DIR}/run_local_audio_sanity.sh"
@ -199,7 +230,7 @@ if [[ "${PROBE_PREBUILD}" != "0" ]]; then
echo "==> prebuilding sync probe/analyzer before opening the capture window"
(
cd "${REPO_ROOT}"
cargo build -p lesavka_client --bin lesavka-sync-probe --bin lesavka-sync-analyze
cargo build -p lesavka_client --bin lesavka-sync-probe --bin lesavka-sync-analyze --bin lesavka-relayctl
)
fi
@ -218,6 +249,7 @@ if [[ -n "${SERVER_TUNNEL_PID}" ]]; then
echo " ↪ tunneled to ${LESAVKA_SERVER_HOST}:127.0.0.1:${SERVER_TUNNEL_REMOTE_PORT}"
fi
print_lesavka_versions
preflight_server_path
echo "==> starting Tethys capture on ${TETHYS_HOST}"

View File

@ -154,6 +154,37 @@ start_server_tunnel_if_needed() {
echo " ↪ tunneled to ${LESAVKA_SERVER_HOST}:127.0.0.1:${LESAVKA_SERVER_PORT}"
}
print_lesavka_versions() {
echo "==> Lesavka versions under test"
if [[ ! -x "${REPO_ROOT}/target/debug/lesavka-relayctl" ]]; then
(cd "${REPO_ROOT}" && cargo build -p lesavka_client --bin lesavka-relayctl >/dev/null)
fi
local version_output
if ! version_output="$(
LESAVKA_TLS_DOMAIN="${LESAVKA_TLS_DOMAIN}" \
"${REPO_ROOT}/target/debug/lesavka-relayctl" \
--server "${RESOLVED_LESAVKA_SERVER_ADDR}" \
version 2>&1
)"; then
echo "failed to query Lesavka versions through ${RESOLVED_LESAVKA_SERVER_ADDR}" >&2
echo "${version_output}" >&2
return 1
fi
if ! grep -q "^client_version=" <<<"${version_output}"; then
echo "Lesavka version query did not report client_version=; refusing to run an unattributed probe" >&2
echo "${version_output}" >&2
return 1
fi
if ! grep -q "^server_version=" <<<"${version_output}"; then
echo "Lesavka version query did not report server_version=; refusing to run an unattributed probe" >&2
echo "${version_output}" >&2
return 1
fi
while IFS= read -r line; do
[[ -n "${line}" ]] && echo "${line}"
done <<<"${version_output}"
}
start_local_stimulus() {
echo "==> starting local A/V stimulus server"
python3 "${REPO_ROOT}/scripts/manual/local_av_stimulus.py" \
@ -230,10 +261,11 @@ run_browser_capture_with_real_driver() {
echo "==> prebuilding real client and analyzer"
(
cd "${REPO_ROOT}"
cargo build -p lesavka_client --bin lesavka-client --bin lesavka-sync-analyze >/dev/null
cargo build -p lesavka_client --bin lesavka-client --bin lesavka-sync-analyze --bin lesavka-relayctl >/dev/null
)
start_server_tunnel_if_needed
print_lesavka_versions
start_local_stimulus
start_real_lesavka_client
run_browser_capture_with_real_driver

View File

@ -10,7 +10,7 @@ bench = false
[package]
name = "lesavka_server"
version = "0.16.23"
version = "0.16.24"
edition = "2024"
autobins = false

View File

@ -47,6 +47,10 @@ fn upstream_sync_script_tunnels_auto_server_addr_through_ssh() {
"Lesavka audio source not found in PipeWire or ALSA; capture host does not currently expose the gadget microphone.",
"artifact_dir: ${LOCAL_REPORT_DIR}",
"events_csv: ${LOCAL_EVENTS_CSV}",
"==> Lesavka versions under test",
"lesavka-relayctl",
"--bin lesavka-relayctl",
"server_version=",
] {
assert!(
SYNC_SCRIPT.contains(expected),
@ -92,6 +96,10 @@ fn mirrored_sync_script_uses_real_client_capture_path() {
"run_upstream_browser_av_sync.sh",
"wait_for_stimulus_page_ready 15",
"Point the real webcam at the stimulus window",
"==> Lesavka versions under test",
"lesavka-relayctl",
"--bin lesavka-relayctl",
"server_version=",
] {
assert!(
MIRRORED_SYNC_SCRIPT.contains(expected),