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} - + {/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("실행되지 않음");