fix(cli): address PR review findings on omnigraph update (MR-612)

- Refuse non-interactive update without --yes (Devin Review P1):
  bail with a clear error instead of silently replacing binaries when
  stdin is not a TTY. Matches gh / rustup / apt-get posture.
- Persist 24h cooldown on refresh failures (cubic P2): the hidden
  `__refresh-update-cache` subprocess now writes a fresh
  `checked_at_unix` even when the GitHub API call fails (keeping the
  previously-known tag), so a transient outage doesn't spam refreshes.
- Make the checksum-mismatch test platform-correct (cubic P2): compare
  byte-for-byte against the snapshotted original binary instead of
  ELF/shebang magic, which excluded macOS arm64 (Mach-O).
- Bump cli-reference top-level family count 17 -> 18 (cubic P3).
- Add a new integration test asserting non-interactive bail-out.

Co-Authored-By: Ragnor Comerford <ragnor.comerford@gmail.com>
This commit is contained in:
Devin AI 2026-05-10 21:06:42 +00:00
parent fa27c7d318
commit ca92ad34aa
4 changed files with 114 additions and 18 deletions

View file

@ -369,6 +369,10 @@ fn update_fails_loudly_on_checksum_mismatch() {
let dir = TempDir::new().unwrap();
install_real_binary(dir.path());
install_fake_server(dir.path(), b"#!/bin/sh\necho old-server\n");
// Snapshot the original bytes so we can assert byte-for-byte preservation
// after a failed update, without making OS-specific assumptions about the
// executable format (ELF on Linux, Mach-O on macOS, etc.).
let original_omnigraph = fs::read(dir.path().join("omnigraph")).unwrap();
let new_omnigraph = b"NEW-OMNIGRAPH-PAYLOAD";
let (archive, _digest) = build_release_archive(new_omnigraph, None);
@ -404,9 +408,14 @@ fn update_fails_loudly_on_checksum_mismatch() {
);
// The original binary must not be replaced when the checksum fails.
let preserved = fs::read(dir.path().join("omnigraph")).unwrap();
assert!(
preserved.starts_with(b"\x7fELF") || preserved.starts_with(b"#!"),
"original binary should still be in place"
assert_eq!(
preserved, original_omnigraph,
"original binary should be preserved byte-for-byte when the checksum fails"
);
assert_ne!(
preserved.as_slice(),
&new_omnigraph[..],
"rejected payload must not have been written"
);
}
@ -450,6 +459,53 @@ fn update_does_not_replace_omnigraph_server_when_not_present() {
assert!(!dir.path().join("omnigraph-server").exists());
}
#[test]
fn update_refuses_to_run_non_interactively_without_yes() {
let asset = match current_platform_asset() {
Some(a) => a,
None => return,
};
let dir = TempDir::new().unwrap();
install_real_binary(dir.path());
let original_omnigraph = fs::read(dir.path().join("omnigraph")).unwrap();
let new_omnigraph = b"NEW-OMNIGRAPH-PAYLOAD";
let (archive, digest) = build_release_archive(new_omnigraph, None);
let fixture = Fixture::start();
fixture.route(
"/repos/ModernRelay/omnigraph/releases/latest",
200,
"application/json",
release_json("v999.0.0"),
);
fixture.route(
&asset_path("v999.0.0", asset),
200,
"application/octet-stream",
archive,
);
let stem = asset.trim_end_matches(".tar.gz");
fixture.route(
&asset_path("v999.0.0", &format!("{stem}.sha256")),
200,
"text/plain",
format!("{digest} {asset}\n").into_bytes(),
);
// No --yes, no TTY (assert_cmd's stdin is not a terminal): must bail out
// before any binary is replaced.
let out = run_update(dir.path(), &fixture, &[]);
assert!(!out.status.success(), "should refuse non-interactive update without --yes");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("non-interactive") || stderr.contains("--yes"),
"expected non-interactive refusal; got: {stderr}"
);
let preserved = fs::read(dir.path().join("omnigraph")).unwrap();
assert_eq!(preserved, original_omnigraph);
}
#[test]
fn update_check_handles_missing_release_metadata() {
if current_platform_asset().is_none() {