From da3fbdda49273c8b448dc67743b13f03aca62c0e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:08:00 +0900 Subject: [PATCH 1/8] test: reject successful wording for failed Brew cleanup --- .../tests/brew_cleanup_ui_status_semantics.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src-tauri/tests/brew_cleanup_ui_status_semantics.rs diff --git a/src-tauri/tests/brew_cleanup_ui_status_semantics.rs b/src-tauri/tests/brew_cleanup_ui_status_semantics.rs new file mode 100644 index 000000000..3642bc595 --- /dev/null +++ b/src-tauri/tests/brew_cleanup_ui_status_semantics.rs @@ -0,0 +1,21 @@ +use std::fs; +use std::path::PathBuf; + +fn source(path: &str) -> String { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + fs::read_to_string(root.join(path)).expect("repository source must be readable") +} + +#[test] +fn brew_cleanup_nonzero_exit_is_not_presented_as_completed() { + let ui = source("../src/lib/BrewCleanup.svelte"); + + assert!( + ui.contains("실행 실패 (종료 코드"), + "a non-zero Homebrew exit must be announced as a failed execution" + ); + assert!( + !ui.contains("execution.executed ? `실행 완료 (종료 코드 ${execution.status_code})`"), + "the UI must not label every executed command as completed regardless of exit status" + ); +} From c6a5ea15a2ce144e60d017079255e2bae637919e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 20:11:30 +0900 Subject: [PATCH 2/8] test: avoid duplicate cross-layer Brew status contract --- .../tests/brew_cleanup_ui_status_semantics.rs | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 src-tauri/tests/brew_cleanup_ui_status_semantics.rs diff --git a/src-tauri/tests/brew_cleanup_ui_status_semantics.rs b/src-tauri/tests/brew_cleanup_ui_status_semantics.rs deleted file mode 100644 index 3642bc595..000000000 --- a/src-tauri/tests/brew_cleanup_ui_status_semantics.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -fn source(path: &str) -> String { - let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - fs::read_to_string(root.join(path)).expect("repository source must be readable") -} - -#[test] -fn brew_cleanup_nonzero_exit_is_not_presented_as_completed() { - let ui = source("../src/lib/BrewCleanup.svelte"); - - assert!( - ui.contains("실행 실패 (종료 코드"), - "a non-zero Homebrew exit must be announced as a failed execution" - ); - assert!( - !ui.contains("execution.executed ? `실행 완료 (종료 코드 ${execution.status_code})`"), - "the UI must not label every executed command as completed regardless of exit status" - ); -} From 6387aeaa0e8367a0916efe023e96f74fd9cf829b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 12:16:37 +0900 Subject: [PATCH 3/8] test: require privacy-safe Brew cleanup failures --- .../brewCleanupErrorPrivacyContract.test.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/lib/brewCleanupErrorPrivacyContract.test.ts diff --git a/src/lib/brewCleanupErrorPrivacyContract.test.ts b/src/lib/brewCleanupErrorPrivacyContract.test.ts new file mode 100644 index 000000000..a83049f38 --- /dev/null +++ b/src/lib/brewCleanupErrorPrivacyContract.test.ts @@ -0,0 +1,30 @@ +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); + +function readSource(path: string): string { + return readFileSync(resolve(repositoryRoot, path), "utf8"); +} + +describe("BrewCleanup privacy-safe failure feedback", () => { + it("never renders arbitrary backend exception text", () => { + const source = readSource("src/lib/BrewCleanup.svelte"); + + expect(source).not.toContain("String(e)"); + expect(source).toContain("Homebrew 정리 계획을 만들지 못했습니다."); + expect(source).toContain("Homebrew 정리를 실행하지 못했습니다."); + expect(source).toContain('role="alert"'); + }); + + it("preserves the existing judgment and execution authority calls", () => { + const source = readSource("src/lib/BrewCleanup.svelte"); + + expect(source).toContain("api.judgeBrewCleanup()"); + expect(source).toContain("api.executeBrewCleanup("); + expect(source).toContain("submittedJudgment.plan_fingerprint"); + expect(source).toContain("submittedJudgment.judgment_id"); + }); +}); From 8cda3ada9ca94ec5a684d827fd48111a859db2d0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 12:31:48 +0900 Subject: [PATCH 4/8] fix: bound Homebrew cleanup failure feedback --- src/lib/BrewCleanup.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/BrewCleanup.svelte b/src/lib/BrewCleanup.svelte index f4bb124b3..c2ed13591 100644 --- a/src/lib/BrewCleanup.svelte +++ b/src/lib/BrewCleanup.svelte @@ -26,7 +26,7 @@ try { judgment = await api.judgeBrewCleanup(); } catch (e) { - error = String(e); + error = "Homebrew 정리 계획을 만들지 못했습니다."; } finally { planning = false; } @@ -73,7 +73,7 @@ rationale.trim(), ); } catch (e) { - error = String(e); + error = "Homebrew 정리를 실행하지 못했습니다."; } finally { judgment = null; confirmationPhrase = ""; From d1ddd2dc7448c981ccbf57366e57d12d9316e389 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 18:54:20 +0900 Subject: [PATCH 5/8] fix: bound Homebrew audit failure alert --- src/lib/BrewCleanup.svelte | 2 +- src/lib/brewCleanupErrorPrivacyContract.test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/BrewCleanup.svelte b/src/lib/BrewCleanup.svelte index c2ed13591..d037afe66 100644 --- a/src/lib/BrewCleanup.svelte +++ b/src/lib/BrewCleanup.svelte @@ -139,7 +139,7 @@ {#if execution.record_path}

감사 기록: {execution.record_path}

{:else} - + {/if} {/if} diff --git a/src/lib/brewCleanupErrorPrivacyContract.test.ts b/src/lib/brewCleanupErrorPrivacyContract.test.ts index a83049f38..3d3eb4d5f 100644 --- a/src/lib/brewCleanupErrorPrivacyContract.test.ts +++ b/src/lib/brewCleanupErrorPrivacyContract.test.ts @@ -14,6 +14,8 @@ describe("BrewCleanup privacy-safe failure feedback", () => { const source = readSource("src/lib/BrewCleanup.svelte"); expect(source).not.toContain("String(e)"); + expect(source).not.toContain("record_error"); + expect(source).not.toContain("저장하지 못했습니다: {"); expect(source).toContain("Homebrew 정리 계획을 만들지 못했습니다."); expect(source).toContain("Homebrew 정리를 실행하지 못했습니다."); expect(source).toContain('role="alert"'); From 8f8821202b2f93b01d6ef6559eb0c22819e020bb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 00:47:37 +0900 Subject: [PATCH 6/8] fix: bound iCloud eviction audit alert --- src/lib/IcloudLocalEviction.svelte | 2 +- src/lib/brewCleanupErrorPrivacyContract.test.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/IcloudLocalEviction.svelte b/src/lib/IcloudLocalEviction.svelte index ca01b849d..ab01d3eb2 100644 --- a/src/lib/IcloudLocalEviction.svelte +++ b/src/lib/IcloudLocalEviction.svelte @@ -160,7 +160,7 @@

결과 기록: {eviction.result_path}

{:else} {/if} {:else if plan.eligible_after_human_approval} diff --git a/src/lib/brewCleanupErrorPrivacyContract.test.ts b/src/lib/brewCleanupErrorPrivacyContract.test.ts index 3d3eb4d5f..11a1b394f 100644 --- a/src/lib/brewCleanupErrorPrivacyContract.test.ts +++ b/src/lib/brewCleanupErrorPrivacyContract.test.ts @@ -12,10 +12,14 @@ function readSource(path: string): string { describe("BrewCleanup privacy-safe failure feedback", () => { it("never renders arbitrary backend exception text", () => { const source = readSource("src/lib/BrewCleanup.svelte"); + const evictionSource = readSource("src/lib/IcloudLocalEviction.svelte"); expect(source).not.toContain("String(e)"); expect(source).not.toContain("record_error"); expect(source).not.toContain("저장하지 못했습니다: {"); + expect(source).not.toMatch(/\{execution\.record_error\}/); + expect(evictionSource).not.toContain("result_record_error"); + expect(evictionSource).not.toMatch(/\{eviction\.result_record_error\}/); expect(source).toContain("Homebrew 정리 계획을 만들지 못했습니다."); expect(source).toContain("Homebrew 정리를 실행하지 못했습니다."); expect(source).toContain('role="alert"'); From 098d3da9263c740cf1145d754ffd481d0f958d15 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 09:06:19 -0700 Subject: [PATCH 7/8] test: reject raw iCloud eviction errors --- src/lib/brewCleanupErrorPrivacyContract.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/brewCleanupErrorPrivacyContract.test.ts b/src/lib/brewCleanupErrorPrivacyContract.test.ts index 11a1b394f..243a264f0 100644 --- a/src/lib/brewCleanupErrorPrivacyContract.test.ts +++ b/src/lib/brewCleanupErrorPrivacyContract.test.ts @@ -18,11 +18,15 @@ describe("BrewCleanup privacy-safe failure feedback", () => { expect(source).not.toContain("record_error"); expect(source).not.toContain("저장하지 못했습니다: {"); expect(source).not.toMatch(/\{execution\.record_error\}/); + expect(evictionSource).not.toContain("String(e)"); expect(evictionSource).not.toContain("result_record_error"); expect(evictionSource).not.toMatch(/\{eviction\.result_record_error\}/); + expect(evictionSource).toContain("파일 선택 창을 열지 못했습니다."); + expect(evictionSource).toContain("iCloud 로컬 사본 상태를 확인하지 못했습니다."); + expect(evictionSource).toContain("iCloud 로컬 사본 축출을 실행하지 못했습니다."); expect(source).toContain("Homebrew 정리 계획을 만들지 못했습니다."); expect(source).toContain("Homebrew 정리를 실행하지 못했습니다."); - expect(source).toContain('role="alert"'); + expect(source).toContain('role=\"alert\"'); }); it("preserves the existing judgment and execution authority calls", () => { From 0fc67a4c0161b9949700f4ce8274f765cfb9918a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 09:07:55 -0700 Subject: [PATCH 8/8] fix: bound iCloud local eviction errors --- src/lib/IcloudLocalEviction.svelte | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/IcloudLocalEviction.svelte b/src/lib/IcloudLocalEviction.svelte index ab01d3eb2..5aa5e335d 100644 --- a/src/lib/IcloudLocalEviction.svelte +++ b/src/lib/IcloudLocalEviction.svelte @@ -34,8 +34,8 @@ if (typeof selected !== "string") return; path = selected; resetDecision(); - } catch (e) { - error = String(e); + } catch { + error = "파일 선택 창을 열지 못했습니다."; } } @@ -46,8 +46,8 @@ resetDecision(); try { plan = await api.planIcloudLocalCopyEviction(cloudRoot, selectedPath); - } catch (e) { - error = String(e); + } catch { + error = "iCloud 로컬 사본 상태를 확인하지 못했습니다."; } finally { planning = false; } @@ -82,8 +82,8 @@ ); confirmation = ""; rationale = ""; - } catch (e) { - error = String(e); + } catch { + error = "iCloud 로컬 사본 축출을 실행하지 못했습니다."; } finally { executing = false; }