diff --git a/desktop/README.md b/desktop/README.md
index 7d0dc741e5..36ebeca4c1 100644
--- a/desktop/README.md
+++ b/desktop/README.md
@@ -116,4 +116,6 @@ The CI-only lifecycle scenario forcibly ends its own installed Desktop process a
Installed boundary checks exercise an 8192-byte Unicode note, reject over-budget input, display zero and capped-ten search results, and verify cancel/confirm behavior when disconnecting with an unsaved draft. Each result requires its matching remote report.
-After upgrading from the earlier preview, select `sqlite-6e237568-v1` and recheck your connection. The previous `sqlite-1.1.1-v1` / `sqlite-63f918b7-v1` / `sqlite-ab43e3a7-v1` / `sqlite-f1089f4e-v1` selection is not silently upgraded to a different contract. See [current qualification](VALIDATION.md).
+After upgrading from an earlier preview, open Connections and select your saved connection. Compare the tested Server build with your deployment, choose `sqlite-legacy-58f7f4f6-v1` under “Available for this Desktop build”, save, then check and explicitly use the connection again. This profile matches the bundled Desktop contract; new connections still start with no compatibility selection.
+
+Profiles for earlier contracts, including `sqlite-6e237568-v1`, appear as disabled historical records. A saved incompatible or unavailable selection remains visible with an explanation until you explicitly replace it; it is not silently upgraded. The earlier `sqlite-1.1.1-v1` / `sqlite-63f918b7-v1` / `sqlite-ab43e3a7-v1` / `sqlite-f1089f4e-v1` selections also require explicit replacement. Historical qualification records and evidence are retained; see the [validation guide](VALIDATION.md).
diff --git a/desktop/README.zh.md b/desktop/README.zh.md
index b25e78d593..fdcdd0e234 100644
--- a/desktop/README.zh.md
+++ b/desktop/README.zh.md
@@ -98,4 +98,6 @@ Windows GitHub Actions 会构建未签名安装包,安装到临时中文路径
安装版边界测试覆盖 8192 字节 Unicode 正文、超限输入禁止提交、搜索无结果和最多 10 条的提示,以及未保存草稿时取消或确认断开连接的行为。是否通过以对应远程报告为准。
-从早期预览版升级后,请选择 `sqlite-6e237568-v1` 并重新检查连接。旧的 `sqlite-1.1.1-v1` / `sqlite-63f918b7-v1` / `sqlite-ab43e3a7-v1` / `sqlite-f1089f4e-v1` 选择不会静默升级为另一份契约。当前验证范围见[验证说明](VALIDATION.md)。
+从早期预览版升级后,请打开「连接」并选中已保存的连接。先核对已测试的 Server 构建与实际部署,再从「此 Desktop 构建可用」中选择 `sqlite-legacy-58f7f4f6-v1`,保存后重新检查并明确使用此连接。该配置与当前 Desktop 打包的契约匹配;新连接仍默认不选择兼容配置。
+
+`sqlite-6e237568-v1` 等使用旧契约的配置会显示为不可选择的历史验证记录。已保存的不匹配或不可用配置仍会显示,并说明原因,须由你明确改选,不会静默升级。更早的 `sqlite-1.1.1-v1` / `sqlite-63f918b7-v1` / `sqlite-ab43e3a7-v1` / `sqlite-f1089f4e-v1` 选择也须明确替换。完整历史验证记录和证据予以保留,详见[验证说明](VALIDATION.md)。
diff --git a/desktop/tests/installed_workflow.py b/desktop/tests/installed_workflow.py
index 34cab14191..ab6e27011b 100644
--- a/desktop/tests/installed_workflow.py
+++ b/desktop/tests/installed_workflow.py
@@ -111,7 +111,7 @@ def connect(self, name: str, endpoint: str) -> None:
self.type("连接名称", name)
self.type("Server 地址", endpoint)
compatibility = fixture_compatibility_profile()
- self.click(f"//label[normalize-space(text())='已验证兼容配置']/select/option[@value='{compatibility}']")
+ self.click(f"//label[normalize-space(text())='已验证兼容配置']/select//option[@value='{compatibility}']")
self.button("保存配置")
self.activate(name)
diff --git a/desktop/ui/src/app/Connections.tsx b/desktop/ui/src/app/Connections.tsx
index cba0bf0caf..c691e2dad2 100644
--- a/desktop/ui/src/app/Connections.tsx
+++ b/desktop/ui/src/app/Connections.tsx
@@ -22,6 +22,7 @@ import type {
StorageChoice,
Fact,
} from "../generated/ipc";
+import { contractSha256 } from "../generated/operations";
import { desktopApi } from "../shared/ipc";
import { connectionMessages, connectionError } from "./connection-messages";
import { messages, type Language } from "./messages";
@@ -155,6 +156,18 @@ export function Connections({ state, language, onState, onDirty }: Props) {
(profile.credentialState === "stored" ||
profile.credentialState === "session_only") &&
!invalidated.current;
+ const availableProfiles = state?.compatibilityProfiles.filter(
+ (p) => p.contractSha256 === contractSha256,
+ );
+ const historicalProfiles = state?.compatibilityProfiles.filter(
+ (p) => p.contractSha256 !== contractSha256,
+ );
+ const selectedCompatibility = state?.compatibilityProfiles.find(
+ (p) => p.id === compatibility,
+ );
+ const unavailableCompatibility =
+ compatibility !== "" &&
+ selectedCompatibility?.contractSha256 !== contractSha256;
return (
@@ -315,14 +328,39 @@ export function Connections({ state, language, onState, onDirty }: Props) {
}}
>
- {state?.compatibilityProfiles.map((p) => (
-
+ )}
+ {!!historicalProfiles?.length && (
+
+ )}
+ {compatibility && !selectedCompatibility && (
+
- ))}
+ )}
{t.compatibilityHint}
+ {unavailableCompatibility && (
+
+ {selectedCompatibility
+ ? t.profileContractMismatch
+ : t.profileMissing}
+
+ )}
{t.ca}