Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions desktop/VALIDATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ The real Server harness covers SQLite with anonymous loopback, static Bearer, HT

Selecting a compatibility profile does not attest the remote binary identity. Existing `sqlite-1.1.1-v1` / `sqlite-63f918b7-v1` / `sqlite-ab43e3a7-v1` / `sqlite-f1089f4e-v1` selections require explicitly choosing the new profile and rechecking the connection. CLI diagnostics separately verify the registered executable path, digest and exact version.

`sqlite-legacy-58f7f4f6-v1` identifies Server sources at `58f7f4f6cbadd698bac35b3fca019d9f48f55b81`, contract digest `9f4ba7b35ecd3b3639af4e648aff84b713a7f30b51d5a4c30c8809aeb8dc8024`, and qualification-wheel digest `7203cf1a98cb9dfd8631e142f7a0f5e74c39845da4fb7d862117a49fa1ef38af`. Its original ten Desktop operations and Legacy Memory protocol were exercised by the native `ConnectionManager` against the isolated built-wheel SQLite Server in all four harness modes: anonymous loopback, Bearer, HTTPS with explicit CA/base path, and injected-provider/enforced access. These checks cover save/search/exact historical citation reads, identity changes, revocation, ambiguous writes without replay, and Scope pagination. The original `sqlite-6e237568-v1` record retains its identities and operations. Current qualification requires explicitly selecting a profile whose recorded contract matches the bundled Desktop contract and rechecking the connection.

This qualification used macOS 26.2 ARM64, Rust 1.95.0 and Python 3.12.7. An external system Python launcher was used for native process creation because this environment can terminate native subprocesses before their first request. The wheel, native client, fixtures and business assertions were preserved. The real CLI's fixed version, service-status and integration commands were also exercised with an isolated home and PATH; their actual outputs passed the native projections. These checks establish the stated SQLite compatibility and CLI response handling. Windows registered-executable checks, credentials, process containment and installed-package acceptance require their actual Windows CI checks. No remote legacy deployment was tested in this qualification.

## Remaining release qualification

- Clean standard-user Windows 11, including absent/present WebView2 and bootstrap recovery.
Expand Down
16 changes: 15 additions & 1 deletion desktop/src-tauri/examples/server_probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,23 @@ async fn main() {
.unwrap(),
);
let raw: serde_json::Value = serde_json::from_slice(&std::fs::read(&args[1]).unwrap()).unwrap();
let compatibility = manager.state().unwrap().compatibility_profiles[0]
let contract: serde_json::Value =
serde_json::from_str(include_str!("../src/transport/operations.json")).unwrap();
let contract_sha256 = contract["contractSha256"].as_str().unwrap();
let state = manager.state().unwrap();
let mut qualified = state
.compatibility_profiles
.iter()
.filter(|profile| profile.contract_sha256 == contract_sha256);
let compatibility = qualified
.next()
.expect("qualified fixture contract")
.id
.clone();
assert!(
qualified.next().is_none(),
"ambiguous fixture qualification"
);
let credential = raw["token"]
.as_str()
.map(|value| serde_json::json!({"secret":value,"storage":"session_only"}));
Expand Down
19 changes: 19 additions & 0 deletions desktop/src-tauri/src/connections/compatibility.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,24 @@
"get_memory_entry"
],
"evidence": "desktop/VALIDATION.md"
},
{
"id": "sqlite-legacy-58f7f4f6-v1",
"serverCommit": "58f7f4f6cbadd698bac35b3fca019d9f48f55b81",
"contractSha256": "9f4ba7b35ecd3b3639af4e648aff84b713a7f30b51d5a4c30c8809aeb8dc8024",
Comment on lines +22 to +24

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #1882 (current head c9ae71a44). Both README instructions now select the profile matching the bundled contract. The selector distinguishes compatible profiles from disabled historical qualifications; saved historical or unknown settings remain visible with an explanation and require an explicit compatible selection before saving. Existing qualification records and native compatibility checks are preserved.

The installed workflow now locates the exact enabled profile beneath the optgroups. Windows CI associated with the current PR head passed all 34 UI unit tests, the real SQLite Server/CLI stages, and the installed-package smoke workflow. This is hosted-runner acceptance; it does not qualify standard-user installation, absent-WebView2 recovery, or visual usability. The separate workflow-dispatch-only installed-package job was skipped by configuration; the installed smoke step in the windows job actually ran and passed.

"artifactSha256": "7203cf1a98cb9dfd8631e142f7a0f5e74c39845da4fb7d862117a49fa1ef38af",
"operations": [
"get_liveness",
"get_readiness",
"get_access_principal",
"get_capabilities",
"list_scopes",
"get_scope",
"get_default_scope",
"remember_memory",
"search_memory",
"get_memory_entry"
],
"evidence": "desktop/VALIDATION.md"
}
]
60 changes: 53 additions & 7 deletions desktop/src-tauri/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ pub fn project(
Ok(report)
}

#[cfg(any(windows, test))]
fn supported_cli_version(version: &str) -> bool {
((version == "1.0.1" || version.starts_with("1.0.1.dev"))
|| (version == "1.1.1" || version.starts_with("1.1.1.dev"))
|| (version == "1.2.1" || version.starts_with("1.2.1.dev")))
&& version.len() <= 128
&& version
.bytes()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, b'.' | b'+' | b'-'))
}

#[cfg(windows)]
#[derive(Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
Expand Down Expand Up @@ -261,13 +272,7 @@ fn execute(
serde_json::from_slice(&bytes).map_err(|_| SafeError::InvalidInput)?;
// This is an explicit local-installation pin, not publisher-signature verification.
if config.source != "explicit_local_installation"
|| !((config.version == "1.0.1" || config.version.starts_with("1.0.1.dev"))
|| (config.version == "1.1.1" || config.version.starts_with("1.1.1.dev")))
|| config.version.len() > 128
|| !config
.version
.bytes()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, b'.' | b'+' | b'-'))
|| !supported_cli_version(&config.version)
|| config.sha256.len() != 64
|| !config.sha256.bytes().all(|v| v.is_ascii_hexdigit())
|| !config.executable.is_absolute()
Expand Down Expand Up @@ -341,3 +346,44 @@ impl DiagnosticHost {
}
}
}

#[cfg(test)]
mod tests {
use super::supported_cli_version;

#[test]
fn qualified_cli_versions_include_the_actual_atomic_ci_build() {
for version in [
"1.0.1",
"1.0.1.dev61+g63f918b7e.d20260919",
"1.1.1",
"1.1.1.dev1+g6e237568",
"1.2.1",
"1.2.1.dev86+g3d3a6290d",
] {
assert!(supported_cli_version(version), "unqualified {version}");
}
}

#[test]
fn unqualified_or_malformed_cli_versions_remain_rejected() {
for version in [
"",
"1.2.0",
"1.2.2.dev86+g3d3a6290d",
"1.2.10",
"1.2.10.dev86+g3d3a6290d",
"1.3.1",
"1.2.1rc1",
"1.2.1.dev86 with spaces",
"1.2.1.dev86\n",
"1.2.1.dev86+未知",
] {
assert!(!supported_cli_version(version), "qualified {version}");
}
assert!(!supported_cli_version(&format!(
"1.2.1.dev{}",
"a".repeat(128)
)));
}
}
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/transport/operations.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"contractSha256": "9af88b2b779c372a21ae9f398d0ca75b333f2e5da2afa3626df879911a442edd",
"contractSha256": "9f4ba7b35ecd3b3639af4e648aff84b713a7f30b51d5a4c30c8809aeb8dc8024",
"operations": {
"get_liveness": {
"method": "GET",
Expand Down
16 changes: 15 additions & 1 deletion desktop/src-tauri/tests/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,23 @@ async fn setup() -> Fixture {
let manager = Arc::new(ConnectionManager::new(
ProfileRepository::open(dir.path().join("profiles.json"), Arc::new(WindowsVault)).unwrap(),
));
let compatibility = manager.state().unwrap().compatibility_profiles[0]
let contract: serde_json::Value =
serde_json::from_str(include_str!("../src/transport/operations.json")).unwrap();
let contract_sha256 = contract["contractSha256"].as_str().unwrap();
let state = manager.state().unwrap();
let mut qualified = state
.compatibility_profiles
.iter()
.filter(|profile| profile.contract_sha256 == contract_sha256);
let compatibility = qualified
.next()
.expect("qualified fixture contract")
.id
.clone();
assert!(
qualified.next().is_none(),
"ambiguous fixture qualification"
);
let mut ids = vec![];
for name in ["A", "B"] {
let state = manager.save_profile(serde_json::from_value(serde_json::json!({"id":null,"revision":null,"name":name,"endpoint":endpoint,"authentication":"unauthenticated_loopback","caPem":null,"compatibility":compatibility,"keepCredential":false,"credential":null})).unwrap()).unwrap();
Expand Down
16 changes: 15 additions & 1 deletion desktop/src-tauri/tests/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,23 @@ async fn fixture() -> Fixture {
}
}
fn add(manager: &ConnectionManager, name: &str, endpoint: &str) -> String {
let compatibility = manager.state().unwrap().compatibility_profiles[0]
let contract: serde_json::Value =
serde_json::from_str(include_str!("../src/transport/operations.json")).unwrap();
let contract_sha256 = contract["contractSha256"].as_str().unwrap();
let state = manager.state().unwrap();
let mut qualified = state
.compatibility_profiles
.iter()
.filter(|profile| profile.contract_sha256 == contract_sha256);
let compatibility = qualified
.next()
.expect("qualified fixture contract")
.id
.clone();
assert!(
qualified.next().is_none(),
"ambiguous fixture qualification"
);
let state = manager.save_profile(serde_json::from_value(serde_json::json!({
"id":null,"revision":null,"name":name,"endpoint":endpoint,
"authentication":"unauthenticated_loopback","caPem":null,"compatibility":compatibility,
Expand Down
10 changes: 10 additions & 0 deletions desktop/tests/installed_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@
from real_server import HarnessFailure, control_pipe


def fixture_compatibility_profile() -> str:
desktop = Path(__file__).resolve().parents[1]
contract = json.loads((desktop / "src-tauri/src/transport/operations.json").read_text(encoding="utf-8"))
profiles = json.loads((desktop / "src-tauri/src/connections/compatibility.json").read_text(encoding="utf-8"))
matches = [profile["id"] for profile in profiles if profile["contractSha256"] == contract["contractSha256"]]
if len(matches) != 1:
raise HarnessFailure("installed_server_compatibility_profile")
return matches[0]


def wait_ready(client: httpx.Client, process: subprocess.Popen[bytes]) -> None:
for _ in range(150):
if process.poll() is not None:
Expand Down
5 changes: 3 additions & 2 deletions desktop/tests/installed_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import httpx
from installed_boundaries import exercise_note_budget, exercise_search_limit
from installed_fixture import isolated_server
from installed_fixture import fixture_compatibility_profile, isolated_server
from real_server import HarnessFailure

ELEMENT = "element-6066-11e4-a52e-4f735466cecf"
Expand Down Expand Up @@ -110,7 +110,8 @@ def connect(self, name: str, endpoint: str) -> None:
self.button("连接")
self.type("连接名称", name)
self.type("Server 地址", endpoint)
self.click("//label[normalize-space(text())='已验证兼容配置']/select/option[@value='sqlite-6e237568-v1']")
compatibility = fixture_compatibility_profile()
self.click(f"//label[normalize-space(text())='已验证兼容配置']/select/option[@value='{compatibility}']")
self.button("保存配置")
self.activate(name)

Expand Down
Loading
Loading