diff --git a/tests/operator_gui/test_static_workbench.py b/tests/operator_gui/test_static_workbench.py
index d933895..e297988 100644
--- a/tests/operator_gui/test_static_workbench.py
+++ b/tests/operator_gui/test_static_workbench.py
@@ -458,7 +458,10 @@ def test_safety_rules_are_visible_in_ui_contract():
assert 'id="decision-memo"' in html
assert 'id="manual-query-provider"' in html
- assert 'option value="google_search"' not in html
+ # 2026-06-12 안전 규칙 개정(설계 승인 2026-06-11): 운영자 수동 검색에
+ # 구글 근거 검색(텍스트 쿼리)을 동적 옵션으로 노출한다. 정적 HTML 기본값은
+ # 네이버만 유지하고, 이미지 업로드 역검색 금지는 그대로다.
+ assert '{ id: "google_search", label: providerLabels.google_search }' in script
assert "네이버" in html
assert "reverse search" not in html.lower()
assert "sourceEvidenceIds" in script
@@ -601,13 +604,18 @@ def test_operator_gui_exposes_keyword_candidate_collection_workflow():
assert "제출 제목/파일명 기반" in search
-def test_google_custom_search_is_not_exposed_as_operator_choice():
+def test_google_custom_search_is_exposed_as_operator_text_query_choice():
html = _read(INDEX)
script = _read(APP_JS)
+ search = _read(OPERATOR_SEARCH_JS)
+ labels = _read(OPERATOR_LABELS_JS)
- assert "구글 맞춤 검색" not in html
- assert "구글 맞춤 검색" not in script
+ # 정적 HTML은 네이버 기본값만 두고 부트스트랩 후 동적으로 채운다.
assert 'value="google_search"' not in html
+ assert '{ id: "google_search", label: providerLabels.google_search }' in script
+ assert "구글 근거 검색" in labels
+ assert 'provider === "google_search" ? "google_search" : "naver"' in search
+ assert "reverse search" not in html.lower()
assert "operatorSearchProviders" in script
assert "visibleProviderControls" in script
diff --git a/web/operator-gui/app.js b/web/operator-gui/app.js
index 9038cc5..be5c0fc 100644
--- a/web/operator-gui/app.js
+++ b/web/operator-gui/app.js
@@ -12,7 +12,10 @@
} = window.OperatorLabels;
const retiredProviderIds = new Set(["google_search"]);
-const operatorSearchProviders = [{ id: "naver", label: providerLabels.naver }];
+const operatorSearchProviders = [
+ { id: "naver", label: providerLabels.naver },
+ { id: "google_search", label: providerLabels.google_search },
+];
const submissions = [];
@@ -616,27 +619,25 @@ function visibleProviderStateEntries(providerState) {
function renderOperatorSearchProviderOptions() {
-
const optionHtml = operatorSearchProviders
-
- .map((provider) => ``)
-
+ .map((provider) => {
+ const runtime = providers.find((item) => item.id === provider.id);
+ const unavailable = Boolean(runtime) && !runtime.enabled;
+ const label = unavailable ? `${provider.label} (비활성)` : provider.label;
+ return ``;
+ })
.join("");
["manual-query-provider", "collection-provider"].forEach((elementId) => {
-
const select = document.getElementById(elementId);
-
if (!select) return;
-
const current = select.value;
-
select.innerHTML = optionHtml;
-
select.value = operatorSearchProviders.some((provider) => provider.id === current) ? current : operatorSearchProviders[0].id;
-
+ if (select.selectedOptions[0] && select.selectedOptions[0].disabled) {
+ select.value = operatorSearchProviders[0].id;
+ }
});
-
}
diff --git a/web/operator-gui/operator-search.js b/web/operator-gui/operator-search.js
index c6c355a..8ef97d6 100644
--- a/web/operator-gui/operator-search.js
+++ b/web/operator-gui/operator-search.js
@@ -26,7 +26,7 @@
}
function normalizeManualSearchProvider(provider) {
- return "naver";
+ return provider === "google_search" ? "google_search" : "naver";
}
global.OperatorSearch = {