Apply Opus pre-release follow-ups for v0.50.253
Three small fixes from Opus review of the merged stage diff: 1. Strip 9 orphan wiki_* i18n keys (72 lines) from PR #1342 — leaked from a different branch, zero references outside i18n.js. 2. /branch endpoint: reject non-string session_id with explicit 400 (was raising TypeError → generic 500 from get_session()). 3. /branch endpoint: reject negative keep_count with explicit 400 (Python slice semantics on negative produces 'all but last N', confusing fork behavior). Plus tests/test_v050253_opus_followups.py — 3 regression tests pinning all three fixes. Verified: 3558 pytest passing.
This commit is contained in:
@@ -1711,6 +1711,11 @@ def handle_post(handler, parsed) -> bool:
|
||||
require(body, "session_id")
|
||||
except ValueError as e:
|
||||
return bad(handler, str(e))
|
||||
# Reject non-string session_id explicitly so the failure surfaces as a
|
||||
# 400 instead of a generic 500 from get_session() raising TypeError.
|
||||
# (Opus pre-release follow-up.)
|
||||
if not isinstance(body["session_id"], str):
|
||||
return bad(handler, "session_id must be a string")
|
||||
try:
|
||||
source = get_session(body["session_id"])
|
||||
except KeyError:
|
||||
@@ -1722,6 +1727,12 @@ def handle_post(handler, parsed) -> bool:
|
||||
keep_count = int(keep_count)
|
||||
except (ValueError, TypeError):
|
||||
return bad(handler, "keep_count must be an integer")
|
||||
# Negative slice (`messages[:-N]`) returns "all but last N", which
|
||||
# is a confusing fork semantic. Reject explicitly so the user
|
||||
# doesn't accidentally fork a session with the tail truncated when
|
||||
# they meant to copy the prefix. (Opus pre-release follow-up.)
|
||||
if keep_count < 0:
|
||||
return bad(handler, "keep_count must be non-negative")
|
||||
|
||||
custom_title = body.get("title")
|
||||
if custom_title:
|
||||
|
||||
@@ -593,15 +593,7 @@ const LOCALES = {
|
||||
onboarding_error_workspace_required: 'Workspace is required.',
|
||||
onboarding_error_model_required: 'Model is required.',
|
||||
onboarding_complete: 'Onboarding complete',
|
||||
wiki_panel_title: 'LLM Wiki',
|
||||
wiki_panel_desc: 'Enable or disable the LLM Wiki knowledge base integration.',
|
||||
wiki_status_label: 'Status',
|
||||
wiki_entry_count: '{0} entries',
|
||||
wiki_last_modified: 'Last modified',
|
||||
wiki_not_available: 'N/A',
|
||||
wiki_enabled: 'Wiki enabled',
|
||||
wiki_disabled: 'Wiki disabled',
|
||||
wiki_toggle_failed: 'Failed to toggle wiki: ',
|
||||
|
||||
// panel/runtime i18n
|
||||
error_prefix: 'Error: ',
|
||||
not_available: 'N/A',
|
||||
@@ -1566,15 +1558,7 @@ const LOCALES = {
|
||||
settings_desc_tts_voice: 'Выберите голос для синтеза речи',
|
||||
settings_label_tts_rate: 'Скорость речи',
|
||||
settings_label_tts_pitch: 'Тон речи',
|
||||
wiki_panel_title: 'LLM Wiki',
|
||||
wiki_panel_desc: '\u0412\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0431\u0430\u0437\u044b \u0437\u043d\u0430\u043d\u0438\u0439 LLM Wiki.',
|
||||
wiki_status_label: '\u0421\u0442\u0430\u0442\u0443\u0441',
|
||||
wiki_entry_count: '{0} \u0437\u0430\u043f\u0438\u0441\u0435\u0439',
|
||||
wiki_last_modified: '\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435',
|
||||
wiki_not_available: '\u041d\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0445',
|
||||
wiki_enabled: '\u0412\u0438\u043a\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0430',
|
||||
wiki_disabled: '\u0412\u0438\u043a\u0438 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0430',
|
||||
wiki_toggle_failed: '\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432\u0438\u043a\u0438: ',
|
||||
|
||||
},
|
||||
|
||||
es: {
|
||||
@@ -2008,15 +1992,7 @@ const LOCALES = {
|
||||
onboarding_error_workspace_required: 'El espacio de trabajo es obligatorio.',
|
||||
onboarding_error_model_required: 'El modelo es obligatorio.',
|
||||
onboarding_complete: 'Onboarding completado',
|
||||
wiki_panel_title: 'Wiki LLM',
|
||||
wiki_panel_desc: 'Habilitar o deshabilitar la integraci\u00f3n de la base de conocimientos LLM Wiki.',
|
||||
wiki_status_label: 'Estado',
|
||||
wiki_entry_count: '{0} entradas',
|
||||
wiki_last_modified: '\u00daltima modificaci\u00f3n',
|
||||
wiki_not_available: 'N/D',
|
||||
wiki_enabled: 'Wiki habilitado',
|
||||
wiki_disabled: 'Wiki deshabilitado',
|
||||
wiki_toggle_failed: 'Error al cambiar wiki: ',
|
||||
|
||||
// panel/runtime i18n
|
||||
error_prefix: 'Error: ',
|
||||
not_available: 'N/A',
|
||||
@@ -3074,15 +3050,7 @@ const LOCALES = {
|
||||
settings_desc_tts_voice: 'Stimme für Sprachsynthese auswählen',
|
||||
settings_label_tts_rate: 'Sprechgeschwindigkeit',
|
||||
settings_label_tts_pitch: 'Tonhöhe',
|
||||
wiki_panel_title: 'LLM Wiki',
|
||||
wiki_panel_desc: 'Aktivieren oder Deaktivieren der LLM Wiki-Wissensdatenbankintegration.',
|
||||
wiki_status_label: 'Status',
|
||||
wiki_entry_count: '{0} Eintr\u00e4ge',
|
||||
wiki_last_modified: 'Zuletzt ge\u00e4ndert',
|
||||
wiki_not_available: 'N/V',
|
||||
wiki_enabled: 'Wiki aktiviert',
|
||||
wiki_disabled: 'Wiki deaktiviert',
|
||||
wiki_toggle_failed: 'Wiki-Umschaltung fehlgeschlagen: ',
|
||||
|
||||
},
|
||||
|
||||
zh: {
|
||||
@@ -3514,15 +3482,7 @@ const LOCALES = {
|
||||
onboarding_error_workspace_required: '必须填写工作区。',
|
||||
onboarding_error_model_required: '必须填写模型。',
|
||||
onboarding_complete: '引导完成',
|
||||
wiki_panel_title: 'LLM Wiki',
|
||||
wiki_panel_desc: '\u542f\u7528\u6216\u7981\u7528 LLM Wiki \u77e5\u8bc6\u5e93\u6574\u5408\u3002',
|
||||
wiki_status_label: '\u72b6\u6001',
|
||||
wiki_entry_count: '{0} \u6761\u76ee',
|
||||
wiki_last_modified: '\u6700\u540e\u4fee\u6539',
|
||||
wiki_not_available: '\u65e0\u6570\u636e',
|
||||
wiki_enabled: 'Wiki \u5df2\u542f\u7528',
|
||||
wiki_disabled: 'Wiki \u5df2\u7981\u7528',
|
||||
wiki_toggle_failed: '\u5207\u6362 Wiki \u5931\u8d25: ',
|
||||
|
||||
// panel/runtime i18n
|
||||
error_prefix: '错误:',
|
||||
not_available: '无',
|
||||
@@ -4682,17 +4642,8 @@ const LOCALES = {
|
||||
settings_desc_tts_voice: '選擇語音合成聲音',
|
||||
settings_label_tts_rate: '語速',
|
||||
settings_label_tts_pitch: '音調',
|
||||
wiki_panel_title: 'LLM Wiki',
|
||||
wiki_panel_desc: '\u555f\u7528\u6216\u505c\u7528 LLM Wiki \u77e5\u8b58\u5eab\u6574\u5408\u3002',
|
||||
wiki_status_label: '\u72b6\u614b',
|
||||
wiki_entry_count: '{0} \u689d\u76ee',
|
||||
wiki_last_modified: '\u6700\u5f8c\u4fee\u6539',
|
||||
wiki_not_available: '\u7121\u6578\u64da',
|
||||
wiki_enabled: 'Wiki \u5df2\u555f\u7528',
|
||||
wiki_disabled: 'Wiki \u5df2\u505c\u7528',
|
||||
wiki_toggle_failed: '\u5207\u63db Wiki \u5931\u6557: ',
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
pt: {
|
||||
_lang: 'pt',
|
||||
@@ -5194,15 +5145,6 @@ const LOCALES = {
|
||||
onboarding_error_model_required: 'Modelo é necessário.',
|
||||
onboarding_complete: 'Configuração completa',
|
||||
|
||||
wiki_panel_title: 'Wiki LLM',
|
||||
wiki_panel_desc: 'Ativar ou desativar a integra\u00e7\u00e3o da base de conhecimento LLM Wiki.',
|
||||
wiki_status_label: 'Estado',
|
||||
wiki_entry_count: '{0} entradas',
|
||||
wiki_last_modified: '\u00daltima modifica\u00e7\u00e3o',
|
||||
wiki_not_available: 'N/D',
|
||||
wiki_enabled: 'Wiki ativado',
|
||||
wiki_disabled: 'Wiki desativado',
|
||||
wiki_toggle_failed: 'Falha ao alternar wiki: ',
|
||||
// panel/runtime i18n
|
||||
error_prefix: 'Erro: ',
|
||||
not_available: 'N/D',
|
||||
@@ -5915,15 +5857,7 @@ const LOCALES = {
|
||||
onboarding_error_workspace_required: 'Workspace is required.',
|
||||
onboarding_error_model_required: 'Model is required.',
|
||||
onboarding_complete: '설정 완료',
|
||||
wiki_panel_title: 'LLM Wiki',
|
||||
wiki_panel_desc: 'LLM Wiki \uc9c0\uc2dd \ubca0\uc774\uc2a4 \ud56d\ubaa9 \ud1b5\ud565\uc744 \ud65c\uc131\ud654\uac78\ub9b4 \uc9c0 \uc2eb\uc74c\uc744 \uc124\uc815\ud569\ub2c8\ub2e4.',
|
||||
wiki_status_label: '\uc0c1\ud0dc',
|
||||
wiki_entry_count: '{0}\uac1c \ud56d\ubaa9',
|
||||
wiki_last_modified: '\ub9c8\uc9c0\ub9c9 \uc218\uc815',
|
||||
wiki_not_available: '\uc0ac\uc6a9 \uc218\uc5c6\uc74c',
|
||||
wiki_enabled: 'Wiki \ud65c\uc131\ud654\ub428',
|
||||
wiki_disabled: 'Wiki \ub044\ud658\ub428',
|
||||
wiki_toggle_failed: 'Wiki \ud1b5\uad6c \uc2dc\ub3c4 \uc2e4\ud328: ',
|
||||
|
||||
// panel/runtime i18n
|
||||
error_prefix: 'Error: ',
|
||||
not_available: 'N/A',
|
||||
|
||||
90
tests/test_v050253_opus_followups.py
Normal file
90
tests/test_v050253_opus_followups.py
Normal file
@@ -0,0 +1,90 @@
|
||||
"""Regression tests for v0.50.253 Opus pre-release follow-ups.
|
||||
|
||||
Three small follow-ups landed alongside the main batch:
|
||||
|
||||
1. /branch endpoint rejects non-string session_id with a 400 (instead of
|
||||
crashing with a generic 500 from get_session() raising TypeError).
|
||||
2. /branch endpoint rejects negative keep_count (Python slicing semantics
|
||||
would otherwise produce "all but last N" rather than a forward prefix).
|
||||
3. PR #1342 leaked 9 unused `wiki_*` i18n keys from a different branch.
|
||||
These were stripped — assert they don't come back.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
# ── 1 + 2: /branch endpoint validation ────────────────────────────────────────
|
||||
|
||||
|
||||
def test_branch_endpoint_rejects_non_string_session_id():
|
||||
"""The handler must reject a non-string session_id with a 400 before
|
||||
reaching get_session()."""
|
||||
src = (REPO / "api" / "routes.py").read_text(encoding="utf-8")
|
||||
branch_handler_idx = src.find('parsed.path == "/api/session/branch":')
|
||||
assert branch_handler_idx != -1, "branch handler not found"
|
||||
# Look at the next ~1500 chars
|
||||
block = src[branch_handler_idx : branch_handler_idx + 1500]
|
||||
assert 'isinstance(body["session_id"], str)' in block, (
|
||||
"branch handler must isinstance-check session_id before passing to "
|
||||
"get_session() — without this, non-string values raise TypeError "
|
||||
"and surface as a confusing 500 instead of a 400."
|
||||
)
|
||||
assert '"session_id must be a string"' in block, (
|
||||
"branch handler must return a clear error message for non-string "
|
||||
"session_id, not a generic bad-request."
|
||||
)
|
||||
|
||||
|
||||
def test_branch_endpoint_rejects_negative_keep_count():
|
||||
"""The handler must reject keep_count < 0 with a 400. Otherwise Python
|
||||
slicing would produce a "all but last N" semantic instead of a forward
|
||||
prefix, which is confusing fork behavior."""
|
||||
src = (REPO / "api" / "routes.py").read_text(encoding="utf-8")
|
||||
branch_handler_idx = src.find('parsed.path == "/api/session/branch":')
|
||||
block = src[branch_handler_idx : branch_handler_idx + 2000]
|
||||
assert "keep_count < 0" in block, (
|
||||
"branch handler must reject negative keep_count — Python's slice "
|
||||
"semantics on negative values are 'all but last N', not 'prefix N', "
|
||||
"and that's a confusing fork behavior."
|
||||
)
|
||||
assert '"keep_count must be non-negative"' in block, (
|
||||
"branch handler must return a clear error message for negative "
|
||||
"keep_count."
|
||||
)
|
||||
|
||||
|
||||
# ── 3: orphan wiki_* i18n keys must not return ────────────────────────────────
|
||||
|
||||
|
||||
def test_no_orphan_wiki_i18n_keys():
|
||||
"""PR #1342 leaked 9 unused `wiki_*` keys (wiki_panel_title, wiki_status_label,
|
||||
wiki_entry_count, wiki_last_modified, wiki_not_available, wiki_enabled,
|
||||
wiki_disabled, wiki_toggle_failed, wiki_panel_desc) into static/i18n.js
|
||||
from a different branch. Zero references existed outside i18n.js. They
|
||||
were stripped by Opus pre-release follow-up. This test pins that they
|
||||
don't return."""
|
||||
i18n_src = (REPO / "static" / "i18n.js").read_text(encoding="utf-8")
|
||||
# If wiki_* keys are added in the future, they MUST have at least one
|
||||
# reference outside i18n.js. Until then, this test fails loudly.
|
||||
forbidden_keys = [
|
||||
"wiki_panel_title",
|
||||
"wiki_panel_desc",
|
||||
"wiki_status_label",
|
||||
"wiki_entry_count",
|
||||
"wiki_last_modified",
|
||||
"wiki_not_available",
|
||||
"wiki_enabled",
|
||||
"wiki_disabled",
|
||||
"wiki_toggle_failed",
|
||||
]
|
||||
for key in forbidden_keys:
|
||||
assert key not in i18n_src, (
|
||||
f"{key!r} is back in static/i18n.js but no consumer uses it. "
|
||||
"If you're adding wiki UI, also wire it up in the JS / panel HTML / "
|
||||
"Python so the key is actually used. See v0.50.253 Opus pre-release "
|
||||
"review."
|
||||
)
|
||||
Reference in New Issue
Block a user