From ce3bb8ecb924ca7c943ba6b0345c878b1bee1619 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 12 Sep 2026 18:13:20 -0700 Subject: [PATCH 1/2] fix: clarify observation and runtime refusal diagnostics --- .../Base/CommandOutputFormatting.swift | 2 +- .../CoreCLITests/ResultEnvelopeTests.swift | 7 +++++ CHANGELOG.md | 2 ++ .../PeekabooBridgeServer+MutationProof.swift | 3 +- ...oBridgeDesktopObservationResultTests.swift | 29 +++++++++++++++++++ docs/commands/see.md | 2 ++ 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/Apps/CLI/Sources/PeekabooCLI/Commands/Base/CommandOutputFormatting.swift b/Apps/CLI/Sources/PeekabooCLI/Commands/Base/CommandOutputFormatting.swift index 7e4bffd8f..acbfa4cac 100644 --- a/Apps/CLI/Sources/PeekabooCLI/Commands/Base/CommandOutputFormatting.swift +++ b/Apps/CLI/Sources/PeekabooCLI/Commands/Base/CommandOutputFormatting.swift @@ -59,7 +59,7 @@ enum ActionOutcomeHumanRenderer { case .refreshTarget: "refresh the target before retrying" case .updateRuntime: - "update the runtime before retrying" + "verify the selected runtime before retrying" case .reconnectSession: "reconnect the Bridge session before retrying" case .recoverSideEffect: diff --git a/Apps/CLI/Tests/CoreCLITests/ResultEnvelopeTests.swift b/Apps/CLI/Tests/CoreCLITests/ResultEnvelopeTests.swift index afa825577..6ecd232ea 100644 --- a/Apps/CLI/Tests/CoreCLITests/ResultEnvelopeTests.swift +++ b/Apps/CLI/Tests/CoreCLITests/ResultEnvelopeTests.swift @@ -99,6 +99,13 @@ struct ResultEnvelopeTests { "⛔ Click refused before dispatch; reconnect the Bridge session before retrying") } + @Test func `runtime refusal asks for verification without assuming an outdated build`() { + let outcome = DesktopActionOutcome.refused(route: .bridge, reason: .runtimeIncompatible) + #expect(ActionOutcomeHumanRenderer.statusLine(for: outcome, operation: "Capture") == + "⛔ Capture refused before dispatch; verify the selected runtime before retrying") + #expect(outcome.refusalReason == .runtimeIncompatible) + } + @Test func `Space switch human detail never claims unverified dispatch completed`() { let dispatched = DesktopActionOutcome.dispatchedUnverified( delivery: .init(mechanism: .nativeFramework, mode: .foreground), diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f8ca46c4..09e866902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Clarify observation evidence failures and runtime refusal guidance so same-build verification errors do not imply that an update will fix them. #710. + - Fix host-routed screen observations with Accessibility elements by validating their semantic owner separately from the screen raster target. #715, #710. - Fix application name and bundle resolution being blocked by reaped processes lingering in LaunchServices; require repeated native absence while retaining refusal for uncertain or changing process identities. #709. diff --git a/Core/PeekabooCore/Sources/PeekabooBridge/PeekabooBridgeServer+MutationProof.swift b/Core/PeekabooCore/Sources/PeekabooBridge/PeekabooBridgeServer+MutationProof.swift index 8bf99fbe5..e86b0af9e 100644 --- a/Core/PeekabooCore/Sources/PeekabooBridge/PeekabooBridgeServer+MutationProof.swift +++ b/Core/PeekabooCore/Sources/PeekabooBridge/PeekabooBridgeServer+MutationProof.swift @@ -77,8 +77,7 @@ extension PeekabooBridgeServer { } throw PeekabooBridgeErrorEnvelope( code: .internalError, - message: "The desktop observation provider returned response evidence that did not match " + - "the requested \(mismatch).") + message: "The desktop observation provider returned inconsistent response evidence: \(mismatch).") } func windowMutationResponse( diff --git a/Core/PeekabooCore/Tests/PeekabooTests/PeekabooBridgeDesktopObservationResultTests.swift b/Core/PeekabooCore/Tests/PeekabooTests/PeekabooBridgeDesktopObservationResultTests.swift index e3fafb9e9..79d36ba56 100644 --- a/Core/PeekabooCore/Tests/PeekabooTests/PeekabooBridgeDesktopObservationResultTests.swift +++ b/Core/PeekabooCore/Tests/PeekabooTests/PeekabooBridgeDesktopObservationResultTests.swift @@ -8,6 +8,35 @@ import Testing @Suite(.serialized) @MainActor struct PeekabooBridgeDesktopObservationResultTests { + @Test + func `observation refusal treats unexpected evidence as a diagnostic rather than a target`() throws { + let original = Self.readOnlyFixtureResult() + let application = try Self.fixture().result.capture.metadata.applicationInfo + let observation = DesktopObservationResult( + target: original.target, + capture: .init( + imageData: Data(), + metadata: .init( + size: original.capture.metadata.size, + mode: .screen, + applicationInfo: application, + displayInfo: original.capture.metadata.displayInfo, + diagnostics: original.capture.metadata.diagnostics)), + elements: original.elements) + + let error = #expect(throws: PeekabooBridgeErrorEnvelope.self) { + try PeekabooBridgeRequestContext.$usesAttestedOperationResultSemantics.withValue(true) { + try PeekabooBridgeServer.validateAttestedObservationBinding( + Self.readOnlyRequest, + result: observation, + requireContentDigest: false) + } + } + let expected = "The desktop observation provider returned inconsistent response evidence: " + + "unexpected application evidence." + #expect(error?.message == expected) + } + @Test func `signed observation authenticates exact file bytes and rejects same-size replacement`() async throws { let fixture = try Self.fixture() diff --git a/docs/commands/see.md b/docs/commands/see.md index 4345cb88b..2c1354e71 100644 --- a/docs/commands/see.md +++ b/docs/commands/see.md @@ -165,6 +165,8 @@ peekaboo see --app "Google Chrome" --json --path /tmp/chrome-see.png \ ## Troubleshooting tips +- An inconsistent-response-evidence refusal means the returned capture could not be verified against the request. Check the specific evidence named in the error. Use `--verbose` to identify the selected runtime and Bridge socket before inspecting that host; a current client and host can still hit a runtime bug, so an update is not assumed to resolve every verification refusal. + Observation verification and MCP image reloads limit each artifact to 256 MiB. Reads retain one regular-file descriptor and reject files that grow or change during the read. Ordinary symlink paths remain supported; oversized or replaced artifacts fail before their content is published. From 972f9b7ccff68e86fa6f1d24356ac1bd1b330875 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 12 Sep 2026 18:41:32 -0700 Subject: [PATCH 2/2] chore: keep diagnostic release notes clear of parallel fixes --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09e866902..6b7d9a0b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,11 @@ ## Unreleased -- Clarify observation evidence failures and runtime refusal guidance so same-build verification errors do not imply that an update will fix them. #710. - - Fix host-routed screen observations with Accessibility elements by validating their semantic owner separately from the screen raster target. #715, #710. - Fix application name and bundle resolution being blocked by reaped processes lingering in LaunchServices; require repeated native absence while retaining refusal for uncertain or changing process identities. #709. +- Clarify observation evidence failures and runtime refusal guidance so same-build verification errors do not imply that an update will fix them. #710. + ## 4.3.4 - 2026-09-11 **Highlights:** Restore provider-compatible MCP tools and frontmost daemon captures.