From b8f346b682297a145ec31449c7ed9ec9125123e8 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 12 Aug 2026 20:08:00 +0900
Subject: [PATCH 01/11] 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 52f27f17d30592f9497c5290b6900945cf7d8585 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 12 Aug 2026 20:08:35 +0900
Subject: [PATCH 02/11] fix: distinguish failed Brew cleanup execution
---
src/lib/BrewCleanup.svelte | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/lib/BrewCleanup.svelte b/src/lib/BrewCleanup.svelte
index 3ac08ac9f..f4bb124b3 100644
--- a/src/lib/BrewCleanup.svelte
+++ b/src/lib/BrewCleanup.svelte
@@ -128,7 +128,11 @@
{#if execution}
- {execution.executed ? `실행 완료 (종료 코드 ${execution.status_code})` : "실행되지 않음"}
+ {execution.executed
+ ? execution.status_code === 0
+ ? `실행 성공 (종료 코드 ${execution.status_code})`
+ : `실행 실패 (종료 코드 ${execution.status_code})`
+ : "실행되지 않음"}
{#if execution.stdout}{execution.stdout}{/if}
{#if execution.stderr}{execution.stderr}{/if}
From b5bb7e766271f81bb7a3eeb9bac85141e07362bb Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 12 Aug 2026 20:11:20 +0900
Subject: [PATCH 03/11] test: keep Brew status semantics with frontend
contracts
---
src/lib/brewCleanupSafetyUiContract.test.ts | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/lib/brewCleanupSafetyUiContract.test.ts b/src/lib/brewCleanupSafetyUiContract.test.ts
index 96c9ba64b..7c4a210ec 100644
--- a/src/lib/brewCleanupSafetyUiContract.test.ts
+++ b/src/lib/brewCleanupSafetyUiContract.test.ts
@@ -41,4 +41,15 @@ describe("Homebrew cleanup safety UX", () => {
expect(source).toContain("승인 문구가 일치하지 않습니다.");
expect(source).toContain("실행 사유를 입력하십시오.");
});
+
+ it("distinguishes a failed subprocess from a successful execution", () => {
+ const source = readSource("src/lib/BrewCleanup.svelte");
+
+ expect(source).toContain("execution.status_code === 0");
+ expect(source).toContain("실행 성공 (종료 코드");
+ expect(source).toContain("실행 실패 (종료 코드");
+ expect(source).not.toContain(
+ "execution.executed ? `실행 완료 (종료 코드 ${execution.status_code})`",
+ );
+ });
});
From f748769f01296cad1503e289a4c8db857815e946 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Wed, 12 Aug 2026 20:11:30 +0900
Subject: [PATCH 04/11] 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 635d9188f6cf2b5dbc995741d28531d177695df0 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 21 Aug 2026 04:38:03 +0900
Subject: [PATCH 05/11] test: cover Brew cleanup execution result states
---
src/lib/brewCleanupSafetyUiContract.test.ts | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/src/lib/brewCleanupSafetyUiContract.test.ts b/src/lib/brewCleanupSafetyUiContract.test.ts
index 7c4a210ec..23060584a 100644
--- a/src/lib/brewCleanupSafetyUiContract.test.ts
+++ b/src/lib/brewCleanupSafetyUiContract.test.ts
@@ -33,6 +33,21 @@ describe("Homebrew cleanup safety UX", () => {
expect(executeCleanup).toContain("judgment = null;");
});
+ it("distinguishes executed and non-executed result states", () => {
+ const source = readSource("src/lib/BrewCleanup.svelte");
+ const start = source.indexOf("{#if execution}");
+ const end = source.indexOf("{/if}", start);
+ const executionResult = source.slice(start, end);
+
+ expect(start).toBeGreaterThanOrEqual(0);
+ expect(end).toBeGreaterThan(start);
+ expect(executionResult).toContain("execution.executed");
+ expect(executionResult).toContain("실행 성공 (종료 코드");
+ expect(executionResult).toContain("실행 실패 (종료 코드");
+ expect(executionResult).toContain("실행되지 않음");
+ expect(executionResult).not.toContain("실행 완료
");
+ });
+
it("normalizes the approval phrase and explains why execution is unavailable", () => {
const source = readSource("src/lib/BrewCleanup.svelte");
From a9902902d378cfe7c97061ccb3efb2f3b26d30bd Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 24 Aug 2026 13:37:51 +0900
Subject: [PATCH 06/11] fix: gate Homebrew success styling on execution
---
src/lib/BrewCleanup.svelte | 2 +-
...brewCleanupExecutionStatusContract.test.ts | 20 +++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
create mode 100644 src/lib/brewCleanupExecutionStatusContract.test.ts
diff --git a/src/lib/BrewCleanup.svelte b/src/lib/BrewCleanup.svelte
index 990d3ced3..bb59dc686 100644
--- a/src/lib/BrewCleanup.svelte
+++ b/src/lib/BrewCleanup.svelte
@@ -144,7 +144,7 @@
{/if}
{#if execution}
-
+
{execution.executed
? execution.status_code === 0
? `실행 성공 (종료 코드 ${execution.status_code})`
diff --git a/src/lib/brewCleanupExecutionStatusContract.test.ts b/src/lib/brewCleanupExecutionStatusContract.test.ts
new file mode 100644
index 000000000..45addc11b
--- /dev/null
+++ b/src/lib/brewCleanupExecutionStatusContract.test.ts
@@ -0,0 +1,20 @@
+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)), "../..");
+
+describe("BrewCleanup execution status contract", () => {
+ it("does not style a non-executed zero-status response as success", () => {
+ const source = readFileSync(resolve(repositoryRoot, "src/lib/BrewCleanup.svelte"), "utf8");
+
+ expect(source).toContain(
+ "class:success={execution.executed && execution.status_code === 0}",
+ );
+ expect(source).toContain(
+ "class:error={execution.executed && execution.status_code !== 0}",
+ );
+ expect(source).not.toContain("class:success={execution.status_code === 0}");
+ });
+});
From 2a98451aeaf54ed6c01e241bdc401c18f1b92a95 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Sun, 23 Aug 2026 21:40:09 -0700
Subject: [PATCH 07/11] fix: converge Homebrew status and privacy feedback
(#209)
* test: reject successful wording for failed Brew cleanup
* test: avoid duplicate cross-layer Brew status contract
* test: require privacy-safe Brew cleanup failures
* fix: bound Homebrew cleanup failure feedback
* fix: bound Homebrew audit failure alert
* fix: bound iCloud eviction audit alert
* test: reject raw iCloud eviction errors
* fix: bound iCloud local eviction errors
---
src/lib/BrewCleanup.svelte | 6 +--
src/lib/IcloudLocalEviction.svelte | 46 +++----------------
.../brewCleanupErrorPrivacyContract.test.ts | 40 ++++++++++++++++
3 files changed, 49 insertions(+), 43 deletions(-)
create mode 100644 src/lib/brewCleanupErrorPrivacyContract.test.ts
diff --git a/src/lib/BrewCleanup.svelte b/src/lib/BrewCleanup.svelte
index bb59dc686..bc2da9891 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;
}
@@ -82,7 +82,7 @@
rationale.trim(),
);
} catch (e) {
- error = String(e);
+ error = "Homebrew 정리를 실행하지 못했습니다.";
} finally {
judgment = null;
confirmationPhrase = "";
@@ -156,7 +156,7 @@
{#if execution.record_path}
감사 기록: {execution.record_path}
{:else}
- 명령 결과는 반환됐지만 감사 기록을 저장하지 못했습니다: {execution.record_error}
+ 명령 결과는 반환됐지만 감사 기록을 저장하지 못했습니다.
{/if}
{/if}
diff --git a/src/lib/IcloudLocalEviction.svelte b/src/lib/IcloudLocalEviction.svelte
index 1c21a5bcd..5aa5e335d 100644
--- a/src/lib/IcloudLocalEviction.svelte
+++ b/src/lib/IcloudLocalEviction.svelte
@@ -35,7 +35,7 @@
path = selected;
resetDecision();
} catch {
- error = "iCloud 파일 선택을 완료하지 못했습니다. 다시 시도하십시오.";
+ error = "파일 선택 창을 열지 못했습니다.";
}
}
@@ -47,7 +47,7 @@
try {
plan = await api.planIcloudLocalCopyEviction(cloudRoot, selectedPath);
} catch {
- error = "iCloud 로컬 사본 상태를 확인하지 못했습니다. 다시 시도하십시오.";
+ error = "iCloud 로컬 사본 상태를 확인하지 못했습니다.";
} finally {
planning = false;
}
@@ -83,7 +83,7 @@
confirmation = "";
rationale = "";
} catch {
- error = "iCloud 로컬 사본을 회수하지 못했습니다. 상태를 다시 확인하십시오.";
+ error = "iCloud 로컬 사본 축출을 실행하지 못했습니다.";
} finally {
executing = false;
}
@@ -94,39 +94,6 @@
? "macOS File Provider"
: "Foundation ubiquitous item";
}
-
- function uploadLabel(state: api.IcloudLocalState): string {
- if (state.is_uploaded && !state.is_uploading) return "완료";
- if (state.is_uploading) return "업로드 중";
- return "미완료";
- }
-
- function syncLabel(state: api.IcloudLocalState): string {
- if (state.downloading_status_current && !state.is_uploaded && !state.is_uploading) {
- return "로컬 최신본·업로드 미확인";
- }
- if (state.is_uploaded && !state.is_uploading) return "공급자 동기화 완료";
- if (state.is_uploading) return "공급자 업로드 중";
- return "공급자 동기화 미완료";
- }
-
- function blockerLabel(blocker: string): string {
- const labels: Record = {
- "icloud-upload-not-confirmed": "로컬 최신본이지만 공급자 업로드가 아직 확인되지 않았습니다. 업로드 완료 후 다시 확인하십시오.",
- "icloud-upload-still-running": "공급자 업로드가 진행 중입니다. 완료 후 다시 확인하십시오.",
- "icloud-current-version-unconfirmed": "로컬 최신본 여부를 확인하지 못했습니다. File Provider 상태가 안정된 후 다시 확인하십시오.",
- "icloud-file-provider-native-status-unavailable": "File Provider 상태 증거가 완전하지 않습니다. 잠시 후 다시 확인하십시오.",
- "icloud-file-provider-sync-paused-or-unconfirmed": "File Provider 동기화가 일시중지됐거나 상태가 미확인입니다. 동기화를 재개한 후 다시 확인하십시오.",
- "icloud-unresolved-conflict": "동기화 충돌이 해결되지 않았습니다. 충돌을 해결한 후 다시 확인하십시오.",
- "active-file-use-detected": "현재 사용 중인 파일이라 회수할 수 없습니다. 파일을 닫은 후 다시 확인하십시오.",
- "active-use-evidence-incomplete": "파일 사용 상태를 완전히 확인하지 못했습니다. 잠시 후 다시 확인하십시오.",
- };
- return labels[blocker] ?? "필수 iCloud 상태 증거가 완전하지 않아 회수할 수 없습니다. 상태를 다시 확인하십시오.";
- }
-
- function blockerSummary(blockers: string[]): string {
- return [...new Set(blockers.map(blockerLabel))].join(" ");
- }
@@ -163,9 +130,8 @@
· {observationLabel(plan.icloud_state.observation_method)}
- 업로드 {uploadLabel(plan.icloud_state)}
- 공급자 상태 {syncLabel(plan.icloud_state)}
- 로컬 current {plan.icloud_state.downloading_status_current ? "예" : "아니오"}
+ 업로드 {plan.icloud_state.is_uploaded && !plan.icloud_state.is_uploading ? "완료" : "미완료"}
+ 최신 버전 {plan.icloud_state.downloading_status_current ? "확인" : "미확인"}
충돌 {plan.icloud_state.has_unresolved_conflicts ? "있음" : "없음"}
활성 사용 {plan.active_use.active ? "감지" : "없음"}
동기화 일시정지 {plan.icloud_state.is_sync_paused === false ? "아님" : "미확인/해당"}
@@ -228,7 +194,7 @@
{:else}
- 현재 축출 불가: {blockerSummary(plan.blockers)}
+ 현재 축출 불가: {plan.blockers.join(", ")}
{/if}
{/if}
diff --git a/src/lib/brewCleanupErrorPrivacyContract.test.ts b/src/lib/brewCleanupErrorPrivacyContract.test.ts
new file mode 100644
index 000000000..243a264f0
--- /dev/null
+++ b/src/lib/brewCleanupErrorPrivacyContract.test.ts
@@ -0,0 +1,40 @@
+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");
+ 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("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\"');
+ });
+
+ 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 2cf610fb211ebbba7823dc47f328b273f62c9b08 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 24 Aug 2026 13:41:45 +0900
Subject: [PATCH 08/11] test: bound BrewCleanup execution block contract
---
src/lib/brewCleanupSafetyUiContract.test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/lib/brewCleanupSafetyUiContract.test.ts b/src/lib/brewCleanupSafetyUiContract.test.ts
index 23060584a..5fa577f15 100644
--- a/src/lib/brewCleanupSafetyUiContract.test.ts
+++ b/src/lib/brewCleanupSafetyUiContract.test.ts
@@ -36,7 +36,7 @@ describe("Homebrew cleanup safety UX", () => {
it("distinguishes executed and non-executed result states", () => {
const source = readSource("src/lib/BrewCleanup.svelte");
const start = source.indexOf("{#if execution}");
- const end = source.indexOf("{/if}", start);
+ const end = source.indexOf("\n {/if}\n ", start);
const executionResult = source.slice(start, end);
expect(start).toBeGreaterThanOrEqual(0);
From 1e958400526c6ffa40deb0527c77875595d13d77 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 24 Aug 2026 13:45:50 +0900
Subject: [PATCH 09/11] fix: restore actionable iCloud eviction states
---
src/lib/IcloudLocalEviction.svelte | 40 +++++++++++++++++--
...cloudLocalEvictionSafetyUiContract.test.ts | 18 +++++++--
2 files changed, 52 insertions(+), 6 deletions(-)
diff --git a/src/lib/IcloudLocalEviction.svelte b/src/lib/IcloudLocalEviction.svelte
index 5aa5e335d..2fa117999 100644
--- a/src/lib/IcloudLocalEviction.svelte
+++ b/src/lib/IcloudLocalEviction.svelte
@@ -94,6 +94,39 @@
? "macOS File Provider"
: "Foundation ubiquitous item";
}
+
+ function uploadLabel(state: api.IcloudLocalState): string {
+ if (state.is_uploaded && !state.is_uploading) return "완료";
+ if (state.is_uploading) return "업로드 중";
+ return "미완료";
+ }
+
+ function syncLabel(state: api.IcloudLocalState): string {
+ if (state.downloading_status_current && !state.is_uploaded && !state.is_uploading) {
+ return "로컬 최신본·업로드 미확인";
+ }
+ if (state.is_uploaded && !state.is_uploading) return "공급자 동기화 완료";
+ if (state.is_uploading) return "공급자 업로드 중";
+ return "공급자 동기화 미완료";
+ }
+
+ function blockerLabel(blocker: string): string {
+ const labels: Record = {
+ "icloud-upload-not-confirmed": "로컬 최신본이지만 공급자 업로드가 아직 확인되지 않았습니다. 업로드 완료 후 다시 확인하십시오.",
+ "icloud-upload-still-running": "공급자 업로드가 진행 중입니다. 완료 후 다시 확인하십시오.",
+ "icloud-current-version-unconfirmed": "로컬 최신본 여부를 확인하지 못했습니다. File Provider 상태가 안정된 후 다시 확인하십시오.",
+ "icloud-file-provider-native-status-unavailable": "File Provider 상태 증거가 완전하지 않습니다. 잠시 후 다시 확인하십시오.",
+ "icloud-file-provider-sync-paused-or-unconfirmed": "File Provider 동기화가 일시중지됐거나 상태가 미확인입니다. 동기화를 재개한 후 다시 확인하십시오.",
+ "icloud-unresolved-conflict": "동기화 충돌이 해결되지 않았습니다. 충돌을 해결한 후 다시 확인하십시오.",
+ "active-file-use-detected": "현재 사용 중인 파일이라 회수할 수 없습니다. 파일을 닫은 후 다시 확인하십시오.",
+ "active-use-evidence-incomplete": "파일 사용 상태를 완전히 확인하지 못했습니다. 잠시 후 다시 확인하십시오.",
+ };
+ return labels[blocker] ?? "필수 iCloud 상태 증거가 완전하지 않아 회수할 수 없습니다. 상태를 다시 확인하십시오.";
+ }
+
+ function blockerSummary(blockers: string[]): string {
+ return [...new Set(blockers.map(blockerLabel))].join(" ");
+ }
@@ -130,8 +163,9 @@
· {observationLabel(plan.icloud_state.observation_method)}
- 업로드 {plan.icloud_state.is_uploaded && !plan.icloud_state.is_uploading ? "완료" : "미완료"}
- 최신 버전 {plan.icloud_state.downloading_status_current ? "확인" : "미확인"}
+ 업로드 {uploadLabel(plan.icloud_state)}
+ 공급자 상태 {syncLabel(plan.icloud_state)}
+ 로컬 current {plan.icloud_state.downloading_status_current ? "예" : "아니오"}
충돌 {plan.icloud_state.has_unresolved_conflicts ? "있음" : "없음"}
활성 사용 {plan.active_use.active ? "감지" : "없음"}
동기화 일시정지 {plan.icloud_state.is_sync_paused === false ? "아님" : "미확인/해당"}
@@ -194,7 +228,7 @@
{:else}
- 현재 축출 불가: {plan.blockers.join(", ")}
+ 현재 축출 불가: {blockerSummary(plan.blockers)}
{/if}
{/if}
diff --git a/src/lib/icloudLocalEvictionSafetyUiContract.test.ts b/src/lib/icloudLocalEvictionSafetyUiContract.test.ts
index 133075af5..0a2a6ce69 100644
--- a/src/lib/icloudLocalEvictionSafetyUiContract.test.ts
+++ b/src/lib/icloudLocalEvictionSafetyUiContract.test.ts
@@ -23,9 +23,21 @@ describe("iCloud local eviction safety UI", () => {
it("keeps customer next actions bounded for each native-state failure path", () => {
const source = readSource();
- expect(source).toContain("iCloud 파일 선택을 완료하지 못했습니다. 다시 시도하십시오.");
- expect(source).toContain("iCloud 로컬 사본 상태를 확인하지 못했습니다. 다시 시도하십시오.");
- expect(source).toContain("iCloud 로컬 사본을 회수하지 못했습니다. 상태를 다시 확인하십시오.");
+ expect(source).toContain("파일 선택 창을 열지 못했습니다.");
+ expect(source).toContain("iCloud 로컬 사본 상태를 확인하지 못했습니다.");
+ expect(source).toContain("iCloud 로컬 사본 축출을 실행하지 못했습니다.");
expect(source).toContain("function blockerSummary");
});
+
+ it("retains provider-aware progress and deduplicated blocker feedback", () => {
+ const source = readSource();
+
+ expect(source).toContain("function uploadLabel");
+ expect(source).toContain("업로드 중");
+ expect(source).toContain("function syncLabel");
+ expect(source).toContain("공급자 상태");
+ expect(source).toContain("new Set(blockers.map(blockerLabel))");
+ expect(source).toContain("blockerSummary(plan.blockers)");
+ expect(source).not.toContain("plan.blockers.join(\", \")");
+ });
});
From 0f2705df717d683cd713b59c301a8c01fd31e8ae Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 24 Aug 2026 13:48:01 +0900
Subject: [PATCH 10/11] fix: bind cleanup status to calibration evidence
---
src/lib/BrewCleanup.svelte | 23 +++++++++++++------
...brewCleanupExecutionStatusContract.test.ts | 4 ++++
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/src/lib/BrewCleanup.svelte b/src/lib/BrewCleanup.svelte
index bc2da9891..144123eae 100644
--- a/src/lib/BrewCleanup.svelte
+++ b/src/lib/BrewCleanup.svelte
@@ -34,10 +34,10 @@
function approvalGuidance(): string {
if (!judgment || judgment.verdict !== "safe") return "";
- if (!judgment.calibration || judgment.calibration.judgment_id !== judgment.judgment_id) {
+ if (!calibrationMatchesJudgment(judgment)) {
return "이 정확한 LLM 판정에 연결된 fast-mlsirm calibration이 필요합니다.";
}
- if (!judgment.calibration.passed) {
+ if (!calibrationPassed(judgment)) {
return "fast-mlsirm Judge calibration이 통과하지 않아 실행할 수 없습니다.";
}
if (confirmationPhrase.trim() !== judgment.exact_approval_phrase) {
@@ -52,15 +52,21 @@
function executionReady(): boolean {
return judgment !== null
&& judgment.verdict === "safe"
- && judgment.calibration !== undefined
- && judgment.calibration.judgment_id === judgment.judgment_id
- && judgment.calibration.passed
+ && calibrationPassed(judgment)
&& confirmationPhrase.trim() === judgment.exact_approval_phrase
&& rationale.trim().length > 0
&& !executing
&& execution === null;
}
+ function calibrationMatchesJudgment(value: api.BrewCleanupJudgment): boolean {
+ return value.calibration?.judgment_id === value.judgment_id;
+ }
+
+ function calibrationPassed(value: api.BrewCleanupJudgment): boolean {
+ return calibrationMatchesJudgment(value) && value.calibration?.passed === true;
+ }
+
async function executeCleanup() {
if (!judgment || !executionReady()) return;
const okay = await confirm(
@@ -111,8 +117,11 @@
계획 지문: {report.plan_fingerprint}
실행 예정: brew cleanup --prune-prefix
{#if report.calibration}
-
- Judge calibration ({report.calibration.engine}): {report.calibration.passed ? "통과" : "실패"}
+
+ Judge calibration ({report.calibration.engine}):
+ {!calibrationMatchesJudgment(report)
+ ? "현재 판정과 불일치"
+ : report.calibration.passed ? "통과" : "실패"}
· 표본 {report.calibration.sample_count}개 · 일치율 {Math.round(report.calibration.exact_agreement * 100)}%
{:else}
diff --git a/src/lib/brewCleanupExecutionStatusContract.test.ts b/src/lib/brewCleanupExecutionStatusContract.test.ts
index 45addc11b..13f29a19f 100644
--- a/src/lib/brewCleanupExecutionStatusContract.test.ts
+++ b/src/lib/brewCleanupExecutionStatusContract.test.ts
@@ -15,6 +15,10 @@ describe("BrewCleanup execution status contract", () => {
expect(source).toContain(
"class:error={execution.executed && execution.status_code !== 0}",
);
+ expect(source).toContain("실행 성공");
+ expect(source).toContain("실행 실패");
+ expect(source).toContain("실행되지 않음");
+ expect(source).toContain("execution.executed");
expect(source).not.toContain("class:success={execution.status_code === 0}");
});
});
From 1ada64a334fc27a022d42c897fabe32ccc25ae7e Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Mon, 24 Aug 2026 13:57:23 +0900
Subject: [PATCH 11/11] fix: mark skipped cleanup execution as warning
---
src/lib/BrewCleanup.svelte | 6 +++++-
src/lib/brewCleanupExecutionStatusContract.test.ts | 1 +
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/lib/BrewCleanup.svelte b/src/lib/BrewCleanup.svelte
index 144123eae..b8fa573bc 100644
--- a/src/lib/BrewCleanup.svelte
+++ b/src/lib/BrewCleanup.svelte
@@ -153,7 +153,11 @@
{/if}
{#if execution}
-
+
{execution.executed
? execution.status_code === 0
? `실행 성공 (종료 코드 ${execution.status_code})`
diff --git a/src/lib/brewCleanupExecutionStatusContract.test.ts b/src/lib/brewCleanupExecutionStatusContract.test.ts
index 13f29a19f..4e10746f6 100644
--- a/src/lib/brewCleanupExecutionStatusContract.test.ts
+++ b/src/lib/brewCleanupExecutionStatusContract.test.ts
@@ -15,6 +15,7 @@ describe("BrewCleanup execution status contract", () => {
expect(source).toContain(
"class:error={execution.executed && execution.status_code !== 0}",
);
+ expect(source).toContain("class:warning={!execution.executed}");
expect(source).toContain("실행 성공");
expect(source).toContain("실행 실패");
expect(source).toContain("실행되지 않음");