fix: integrate language channel through numbered IPC and isolate education module
正向合并正律化(2026-08-20): - 语言人格频道与执行手脚训练按编号 IPC 正律接入(MOD-0035/0036,OP-0158..0163), 剥离直连 tauri::command,恢复单一 numbered 网关铁律 - 教育模块按冰朔谕与 2026-08-19 隔离回执整体移出(证据归隔离区),留空模块占位 - 光湖主域门户挂公告与版本中心(冰朔要求的公告与版本更新主阵地) - 知识工作台内嵌共同工作频道(PersonaChannelBody embedded + knowledgeFocus) - foundation/stage-one 合同补聊天运行时验收声明;版本对齐 0.9.1 - 测试名册计数与断言更新至合并后现实:JS 180/180,Rust 194/194
This commit is contained in:
parent
dd1375f089
commit
add00a0377
42 changed files with 622 additions and 5580 deletions
|
|
@ -463,6 +463,16 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
|
|
@ -486,7 +496,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
|
|
@ -499,7 +509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
|
@ -1547,7 +1557,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|||
|
||||
[[package]]
|
||||
name = "hololake-native-desktop"
|
||||
version = "0.5.0"
|
||||
version = "0.9.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"calamine",
|
||||
|
|
@ -1557,6 +1567,7 @@ dependencies = [
|
|||
"fs2",
|
||||
"futures-util",
|
||||
"interprocess",
|
||||
"keyring",
|
||||
"minisign-verify",
|
||||
"qrcode",
|
||||
"quick-xml 0.31.0",
|
||||
|
|
@ -2075,6 +2086,18 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "3.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
|
||||
dependencies = [
|
||||
"log",
|
||||
"security-framework 2.11.1",
|
||||
"security-framework 3.7.0",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.8-speedreader"
|
||||
|
|
@ -3493,7 +3516,7 @@ dependencies = [
|
|||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework 3.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3512,7 +3535,7 @@ version = "0.6.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
|
||||
dependencies = [
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"jni",
|
||||
"log",
|
||||
|
|
@ -3521,7 +3544,7 @@ dependencies = [
|
|||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki",
|
||||
"security-framework",
|
||||
"security-framework 3.7.0",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.61.2",
|
||||
|
|
@ -3632,6 +3655,19 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.7.0"
|
||||
|
|
@ -3639,7 +3675,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
|
|
@ -3900,6 +3936,16 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.10"
|
||||
|
|
@ -4140,7 +4186,7 @@ checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7"
|
|||
dependencies = [
|
||||
"bitflags 2.13.1",
|
||||
"block2",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dispatch",
|
||||
|
|
@ -4646,6 +4692,7 @@ dependencies = [
|
|||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ tokio = { version = "1", features = ["io-util", "process", "time"] }
|
|||
futures-util = "0.3"
|
||||
minisign-verify = "0.2.5"
|
||||
qrcode = { version = "0.14", default-features = false, features = ["svg"] }
|
||||
keyring = { version = "3.6.3", features = ["apple-native"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
widestring = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
keyring = { version = "3.6.3", features = ["apple-native"] }
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -113,12 +113,10 @@ struct BoundaryDrillAnswer {
|
|||
completion_policy: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_execution_limb_snapshot(app: AppHandle) -> Result<ExecutionLimbSnapshot, String> {
|
||||
snapshot(&app).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn run_execution_limb_training_drill(
|
||||
app: AppHandle,
|
||||
input: RunTrainingDrillInput,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ mod code_repo_login;
|
|||
mod direct_local_broker;
|
||||
mod direct_local_session;
|
||||
mod dynamic_capability_routing;
|
||||
mod education_translation;
|
||||
mod education_workspace;
|
||||
mod execution_limb_training;
|
||||
mod enterprise_work_channel;
|
||||
mod external_ai_gateway;
|
||||
|
|
@ -66,21 +64,7 @@ pub fn run() {
|
|||
.plugin(tauri_plugin_dialog::init())
|
||||
.manage(numbered_ipc::NumberedIpcState::default())
|
||||
.manage(mobile_sync::MobileSyncState::default())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
numbered_ipc::numbered_ipc,
|
||||
persona_channel_body::get_persona_channel_body,
|
||||
persona_channel_body::register_trial_persona,
|
||||
persona_channel_body::delete_trial_persona,
|
||||
persona_channel_body::accept_persona_language_contract,
|
||||
persona_channel_body::append_persona_language,
|
||||
persona_language_channel::discover_model_providers,
|
||||
persona_language_channel::upsert_model_provider,
|
||||
persona_language_channel::get_conversation,
|
||||
persona_language_channel::send_message,
|
||||
execution_limb_training::get_execution_limb_snapshot,
|
||||
execution_limb_training::run_execution_limb_training_drill,
|
||||
])
|
||||
.setup(|app| {
|
||||
.invoke_handler(tauri::generate_handler![numbered_ipc::numbered_ipc,]) .setup(|app| {
|
||||
// GLS 是 HoloLake 产品内核,不是开发机旁路服务。合同、依赖闭包、
|
||||
// 自举编译器或回执账本任一不可用时,产品启动失败关闭。
|
||||
gls_protocol_kernel::start_on_application_open(app.handle())?;
|
||||
|
|
|
|||
|
|
@ -45,13 +45,6 @@ const PERSONA_CHANNEL_BODY_PACKAGE: &[u8] = include_bytes!(
|
|||
const PERSONA_CHANNEL_BODY_SIGNATURE: &str = include_str!(
|
||||
"../../fixtures/module-packages/HLP-MOD-LOCAL-PERSONA-CHANNEL-BODY-0001-0.1.0.ghmod.sig"
|
||||
);
|
||||
const EDUCATION_WORKBENCH_NUMBER: &str = "HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001";
|
||||
const EDUCATION_WORKBENCH_PACKAGE: &[u8] = include_bytes!(
|
||||
"../../fixtures/module-packages/HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001-0.1.0.ghmod"
|
||||
);
|
||||
const EDUCATION_WORKBENCH_SIGNATURE: &str = include_str!(
|
||||
"../../fixtures/module-packages/HLP-MOD-OFFICIAL-EDUCATION-WORKBENCH-0001-0.1.0.ghmod.sig"
|
||||
);
|
||||
const WEB_NOVEL_WORKBENCH_NUMBER: &str = "HLP-MOD-OFFICIAL-WEB-NOVEL-WORKBENCH-0001";
|
||||
const WEB_NOVEL_WORKBENCH_PACKAGE: &[u8] = include_bytes!(
|
||||
"../../fixtures/module-packages/HLP-MOD-OFFICIAL-WEB-NOVEL-WORKBENCH-0001-0.1.0.ghmod"
|
||||
|
|
@ -123,11 +116,6 @@ const BUNDLED_MODULES: &[BundledModuleSource] = &[
|
|||
package: PERSONA_CHANNEL_BODY_PACKAGE,
|
||||
signature: PERSONA_CHANNEL_BODY_SIGNATURE,
|
||||
},
|
||||
BundledModuleSource {
|
||||
module_number: EDUCATION_WORKBENCH_NUMBER,
|
||||
package: EDUCATION_WORKBENCH_PACKAGE,
|
||||
signature: EDUCATION_WORKBENCH_SIGNATURE,
|
||||
},
|
||||
BundledModuleSource {
|
||||
module_number: WEB_NOVEL_WORKBENCH_NUMBER,
|
||||
package: WEB_NOVEL_WORKBENCH_PACKAGE,
|
||||
|
|
@ -1611,7 +1599,7 @@ mod tests {
|
|||
assert_eq!(package.manifest.adapter, "channel-workbench-v1");
|
||||
assert_eq!(package.manifest.permissions.len(), 4);
|
||||
assert!(is_sha256(&digest));
|
||||
assert_eq!(BUNDLED_MODULES.len(), 11);
|
||||
assert_eq!(BUNDLED_MODULES.len(), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1632,24 +1620,6 @@ mod tests {
|
|||
assert!(is_sha256(&digest));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bundled_education_workbench_is_an_official_signed_module() {
|
||||
let (_, package, digest) = read_verified_package_bytes(
|
||||
EDUCATION_WORKBENCH_PACKAGE,
|
||||
EDUCATION_WORKBENCH_SIGNATURE,
|
||||
RELEASE_TRUST_RAW,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(package.manifest.module_number, EDUCATION_WORKBENCH_NUMBER);
|
||||
assert_eq!(package.manifest.registration_class, "OFFICIAL_LIGHTHOUSE");
|
||||
assert_eq!(package.manifest.adapter, "education-workbench-v1");
|
||||
assert_eq!(package.manifest.permissions.len(), 8);
|
||||
assert_eq!(
|
||||
package.payload["adapterConfig"]["importDefaultScope"],
|
||||
"UNASSIGNED"
|
||||
);
|
||||
assert!(is_sha256(&digest));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bundled_web_novel_family_uses_one_signed_lifecycle_and_exact_numbers() {
|
||||
|
|
|
|||
|
|
@ -88,8 +88,8 @@ fn validate_tree() -> Result<(), String> {
|
|||
|| tree.record_id != "HLP-UNIFIED-NUMBER-TREE-001"
|
||||
|| tree.state != "MACHINE_COMPILED_STARTUP_ENFORCED"
|
||||
|| tree.root_number != "HLP-NUMBER-WORLD-ROOT-001"
|
||||
|| tree.coordinate_count != 285
|
||||
|| tree.route_count != 182
|
||||
|| tree.coordinate_count != 273
|
||||
|| tree.route_count != 170
|
||||
|| tree.routes.len() != tree.route_count
|
||||
|| tree.identity_node_count != 4
|
||||
|| tree.identity_nodes.len() != tree.identity_node_count
|
||||
|
|
|
|||
|
|
@ -934,7 +934,7 @@ mod tests {
|
|||
#[test]
|
||||
fn registry_is_closed_and_contains_every_migrated_command() {
|
||||
let registry = load_registry().unwrap();
|
||||
assert_eq!(registry.operations.len(), 157);
|
||||
assert_eq!(registry.operations.len(), 145);
|
||||
assert!(!registry.runtime.legacy_direct_commands_allowed);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -234,67 +234,6 @@ pub(crate) async fn dispatch(
|
|||
"channel_growth::update_channel_growth_sharing" => {
|
||||
json(crate::channel_growth::update_channel_growth_sharing(app, input(&payload)?).await?)
|
||||
}
|
||||
"education_workspace::get_education_workspace_snapshot" => {
|
||||
json(crate::education_workspace::get_education_workspace_snapshot(app).await?)
|
||||
}
|
||||
"education_workspace::create_education_document" => json(
|
||||
crate::education_workspace::create_education_document(app, input(&payload)?).await?,
|
||||
),
|
||||
"education_workspace::read_education_document" => {
|
||||
json(crate::education_workspace::read_education_document(app, input(&payload)?).await?)
|
||||
}
|
||||
"education_workspace::save_education_document" => {
|
||||
json(crate::education_workspace::save_education_document(app, input(&payload)?).await?)
|
||||
}
|
||||
"education_workspace::archive_education_document" => json(
|
||||
crate::education_workspace::archive_education_document(app, input(&payload)?).await?,
|
||||
),
|
||||
"education_workspace::create_education_table" => {
|
||||
json(crate::education_workspace::create_education_table(app, input(&payload)?).await?)
|
||||
}
|
||||
"education_workspace::read_education_table" => {
|
||||
json(crate::education_workspace::read_education_table(app, input(&payload)?).await?)
|
||||
}
|
||||
"education_workspace::save_education_table" => {
|
||||
json(crate::education_workspace::save_education_table(app, input(&payload)?).await?)
|
||||
}
|
||||
"education_workspace::archive_education_table" => {
|
||||
json(crate::education_workspace::archive_education_table(app, input(&payload)?).await?)
|
||||
}
|
||||
"education_workspace::assign_imported_table_to_education" => json(
|
||||
crate::education_workspace::assign_imported_table_to_education(app, input(&payload)?)
|
||||
.await?,
|
||||
),
|
||||
"education_translation::import_education_tables_from_dialog" => {
|
||||
json(crate::education_translation::import_education_tables_from_dialog(app).await?)
|
||||
}
|
||||
"education_translation::export_education_table_to_dialog" => json(
|
||||
crate::education_translation::export_education_table_to_dialog(app, input(&payload)?)
|
||||
.await?,
|
||||
),
|
||||
"education_translation::get_education_recognition_capability" => {
|
||||
json(crate::education_translation::get_education_recognition_capability(app)?)
|
||||
}
|
||||
"education_workspace::create_education_automation_rule" => json(
|
||||
crate::education_workspace::create_education_automation_rule(app, input(&payload)?)
|
||||
.await?,
|
||||
),
|
||||
"education_workspace::save_education_automation_rule" => json(
|
||||
crate::education_workspace::save_education_automation_rule(app, input(&payload)?)
|
||||
.await?,
|
||||
),
|
||||
"education_workspace::archive_education_automation_rule" => json(
|
||||
crate::education_workspace::archive_education_automation_rule(app, input(&payload)?)
|
||||
.await?,
|
||||
),
|
||||
"education_workspace::preview_education_automation_rule" => json(
|
||||
crate::education_workspace::preview_education_automation_rule(app, input(&payload)?)
|
||||
.await?,
|
||||
),
|
||||
"education_workspace::execute_education_automation_rule" => json(
|
||||
crate::education_workspace::execute_education_automation_rule(app, input(&payload)?)
|
||||
.await?,
|
||||
),
|
||||
"web_novel_workspace::get_web_novel_workspace_snapshot" => {
|
||||
json(crate::web_novel_workspace::get_web_novel_workspace_snapshot(app).await?)
|
||||
}
|
||||
|
|
@ -450,6 +389,24 @@ pub(crate) async fn dispatch(
|
|||
crate::personal_channel::transition_personal_channel_task(app, input(&payload)?)
|
||||
.await?,
|
||||
),
|
||||
"persona_language_channel::discover_model_providers" => {
|
||||
json(crate::persona_language_channel::discover_model_providers(app).await?)
|
||||
}
|
||||
"persona_language_channel::upsert_model_provider" => {
|
||||
json(crate::persona_language_channel::upsert_model_provider(app, input(&payload)?).await?)
|
||||
}
|
||||
"persona_language_channel::get_conversation" => {
|
||||
json(crate::persona_language_channel::get_conversation(app, input(&payload)?).await?)
|
||||
}
|
||||
"persona_language_channel::send_message" => {
|
||||
json(crate::persona_language_channel::send_message(app, input(&payload)?).await?)
|
||||
}
|
||||
"execution_limb_training::get_execution_limb_snapshot" => {
|
||||
json(crate::execution_limb_training::get_execution_limb_snapshot(app).await?)
|
||||
}
|
||||
"execution_limb_training::run_execution_limb_training_drill" => {
|
||||
json(crate::execution_limb_training::run_execution_limb_training_drill(app, input(&payload)?).await?)
|
||||
}
|
||||
"persona_time_authority::issue_persona_time_ticket" => json(
|
||||
crate::persona_time_authority::issue_persona_time_ticket(app, input(&payload)?).await?,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -15,22 +15,28 @@ const BODY_SCHEMA: &str = "hololake.persona-channel-body/v1";
|
|||
const TRIAL_DURATION_MS: i64 = 30 * 24 * 60 * 60 * 1_000;
|
||||
const ZERO_HASH: &str = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
const MAX_LANGUAGE_BYTES: usize = 2_000_000;
|
||||
const MODULE_NUMBER: &str = "HLP-MOD-LOCAL-PERSONA-CHANNEL-BODY-0001";
|
||||
const ADAPTER: &str = "persona-channel-body-v1";
|
||||
|
||||
fn require_active(app: &AppHandle) -> Result<(), String> {
|
||||
crate::module_package_runtime::require_active_module_adapter(app, MODULE_NUMBER, ADAPTER)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct RegisterTrialPersonaInput {
|
||||
pub display_name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct DeleteTrialPersonaInput {
|
||||
pub persona_id: String,
|
||||
pub exact_confirmation: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct AcceptLanguageContractInput {
|
||||
pub contract_version: String,
|
||||
pub contract_text_sha256: String,
|
||||
|
|
@ -40,7 +46,7 @@ pub struct AcceptLanguageContractInput {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct AppendPersonaLanguageInput {
|
||||
pub persona_id: String,
|
||||
pub speaker: String,
|
||||
|
|
@ -73,16 +79,9 @@ pub struct PersonaChannelBodySnapshot {
|
|||
pub history_mutation_allowed: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_persona_channel_body(
|
||||
app: AppHandle,
|
||||
) -> Result<PersonaChannelBodySnapshot, String> {
|
||||
let database = body_database(&app)?;
|
||||
tauri::async_runtime::spawn_blocking(move || snapshot_at(&database, now_unix_ms()?))
|
||||
.await
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_JOIN_FAILED: {error}"))?
|
||||
}
|
||||
|
||||
/// 语言频道读取已注册人格体摘要的共用入口(2026-08-20 正向合并:自语言人格频道线移植)。
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(crate) fn require_registered_persona(
|
||||
app: &AppHandle,
|
||||
persona_id: &str,
|
||||
|
|
@ -110,11 +109,21 @@ pub(crate) fn require_registered_persona(
|
|||
.ok_or_else(|| "HOLOLAKE_PERSONA_NOT_REGISTERED".to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_persona_channel_body(
|
||||
app: AppHandle,
|
||||
) -> Result<PersonaChannelBodySnapshot, String> {
|
||||
require_active(&app)?;
|
||||
let database = body_database(&app)?;
|
||||
tauri::async_runtime::spawn_blocking(move || snapshot_at(&database, now_unix_ms()?))
|
||||
.await
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_JOIN_FAILED: {error}"))?
|
||||
}
|
||||
|
||||
pub async fn register_trial_persona(
|
||||
app: AppHandle,
|
||||
input: RegisterTrialPersonaInput,
|
||||
) -> Result<PersonaChannelBodySnapshot, String> {
|
||||
require_active(&app)?;
|
||||
let database = body_database(&app)?;
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
register_trial_persona_at(&database, input, now_unix_ms()?)
|
||||
|
|
@ -123,11 +132,11 @@ pub async fn register_trial_persona(
|
|||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_JOIN_FAILED: {error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_trial_persona(
|
||||
app: AppHandle,
|
||||
input: DeleteTrialPersonaInput,
|
||||
) -> Result<PersonaChannelBodySnapshot, String> {
|
||||
require_active(&app)?;
|
||||
let database = body_database(&app)?;
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
delete_trial_persona_at(&database, input, now_unix_ms()?)
|
||||
|
|
@ -136,11 +145,11 @@ pub async fn delete_trial_persona(
|
|||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_JOIN_FAILED: {error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn accept_persona_language_contract(
|
||||
app: AppHandle,
|
||||
input: AcceptLanguageContractInput,
|
||||
) -> Result<PersonaChannelBodySnapshot, String> {
|
||||
require_active(&app)?;
|
||||
let database = body_database(&app)?;
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
accept_contract_at(&database, input, now_unix_ms()?)
|
||||
|
|
@ -149,11 +158,11 @@ pub async fn accept_persona_language_contract(
|
|||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_JOIN_FAILED: {error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn append_persona_language(
|
||||
app: AppHandle,
|
||||
input: AppendPersonaLanguageInput,
|
||||
) -> Result<PersonaChannelBodySnapshot, String> {
|
||||
require_active(&app)?;
|
||||
let database = body_database(&app)?;
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
append_language_at(&database, input, now_unix_ms()?)
|
||||
|
|
@ -305,19 +314,20 @@ fn accept_contract_at(
|
|||
return Err("HOLOLAKE_LANGUAGE_CONTRACT_INVALID".into());
|
||||
}
|
||||
let mut connection = open_database(database, now)?;
|
||||
let receipt = sha256_hex(
|
||||
format!(
|
||||
"{}|{}|{}|{}",
|
||||
input.contract_version, input.contract_text_sha256, input.promote_trial_history, now
|
||||
)
|
||||
.as_bytes(),
|
||||
let contract_version = input.contract_version.trim().to_string();
|
||||
let contract_text_sha256 = input.contract_text_sha256.to_ascii_lowercase();
|
||||
let receipt = contract_receipt(
|
||||
&contract_version,
|
||||
&contract_text_sha256,
|
||||
input.promote_trial_history,
|
||||
now,
|
||||
);
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO language_contract VALUES (1,?1,?2,?3,?4,?5)",
|
||||
params![
|
||||
input.contract_version,
|
||||
input.contract_text_sha256.to_ascii_lowercase(),
|
||||
contract_version,
|
||||
contract_text_sha256,
|
||||
now,
|
||||
input.promote_trial_history,
|
||||
receipt
|
||||
|
|
@ -326,7 +336,12 @@ fn accept_contract_at(
|
|||
.map_err(|error| {
|
||||
format!("HOLOLAKE_LANGUAGE_CONTRACT_ALREADY_ACCEPTED_OR_INVALID: {error}")
|
||||
})?;
|
||||
if input.activate_immediately {
|
||||
let lifecycle_state: String = connection
|
||||
.query_row("SELECT state FROM lifecycle WHERE singleton=1", [], |row| {
|
||||
row.get(0)
|
||||
})
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
if input.activate_immediately || lifecycle_state == "CONTRACT_REQUIRED_CHANNEL_STOPPED" {
|
||||
activate_real_trajectory(&mut connection, now)?;
|
||||
} else {
|
||||
refresh_lifecycle(&mut connection, now)?;
|
||||
|
|
@ -520,16 +535,20 @@ fn snapshot_at(database: &Path, now: i64) -> Result<PersonaChannelBodySnapshot,
|
|||
}
|
||||
fn snapshot_with_connection(
|
||||
connection: &mut Connection,
|
||||
_now: i64,
|
||||
now: i64,
|
||||
) -> Result<PersonaChannelBodySnapshot, String> {
|
||||
let (state,started,ends):(String,i64,i64)=connection.query_row("SELECT state,trial_started_at_unix_ms,trial_ends_at_unix_ms FROM lifecycle WHERE singleton=1",[],|row|Ok((row.get(0)?,row.get(1)?,row.get(2)?))).map_err(|error|format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let accepted = connection
|
||||
.query_row(
|
||||
"SELECT EXISTS(SELECT 1 FROM language_contract)",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
if started < 0
|
||||
|| ends.checked_sub(started) != Some(TRIAL_DURATION_MS)
|
||||
|| !matches!(
|
||||
state.as_str(),
|
||||
"REVERSIBLE_TRIAL" | "CONTRACT_REQUIRED_CHANNEL_STOPPED" | "IMMUTABLE_ACTIVE"
|
||||
)
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
|| (state == "REVERSIBLE_TRIAL" && now >= ends)
|
||||
{
|
||||
return Err("HOLOLAKE_PERSONA_BODY_LIFECYCLE_INTEGRITY_FAILED".into());
|
||||
}
|
||||
let accepted = verify_contract_receipt(connection)?;
|
||||
let personas = {
|
||||
let mut statement=connection.prepare("SELECT persona_id,display_name,state,created_at_unix_ms FROM personas ORDER BY created_at_unix_ms").map_err(|error|format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let rows = statement
|
||||
|
|
@ -555,6 +574,22 @@ fn snapshot_with_connection(
|
|||
})
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let last = verify_immutable_chain(connection)?;
|
||||
let invalid_persona_relationship = personas.iter().any(|persona| {
|
||||
persona.created_at_unix_ms < started
|
||||
|| !matches!(
|
||||
persona.state.as_str(),
|
||||
"REVERSIBLE_TRIAL" | "IMMUTABLE_ACTIVE"
|
||||
)
|
||||
|| (state == "IMMUTABLE_ACTIVE" && persona.state != "IMMUTABLE_ACTIVE")
|
||||
|| (state != "IMMUTABLE_ACTIVE" && persona.state != "REVERSIBLE_TRIAL")
|
||||
});
|
||||
if invalid_persona_relationship
|
||||
|| (state == "IMMUTABLE_ACTIVE" && (!accepted || trial_language_count != 0))
|
||||
|| (state == "CONTRACT_REQUIRED_CHANNEL_STOPPED" && accepted)
|
||||
|| (immutable_language_count > 0 && state != "IMMUTABLE_ACTIVE")
|
||||
{
|
||||
return Err("HOLOLAKE_PERSONA_BODY_RELATIONSHIP_INTEGRITY_FAILED".into());
|
||||
}
|
||||
Ok(PersonaChannelBodySnapshot {
|
||||
schema: BODY_SCHEMA,
|
||||
state,
|
||||
|
|
@ -571,6 +606,54 @@ fn snapshot_with_connection(
|
|||
})
|
||||
}
|
||||
|
||||
fn contract_receipt(
|
||||
contract_version: &str,
|
||||
contract_text_sha256: &str,
|
||||
promote_trial_history: bool,
|
||||
accepted_at_unix_ms: i64,
|
||||
) -> String {
|
||||
sha256_hex(
|
||||
format!(
|
||||
"{contract_version}|{contract_text_sha256}|{promote_trial_history}|{accepted_at_unix_ms}"
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_contract_receipt(connection: &Connection) -> Result<bool, String> {
|
||||
let contract = connection
|
||||
.query_row(
|
||||
"SELECT contract_version, contract_text_sha256, accepted_at_unix_ms, promote_trial_history, acceptance_receipt_sha256 FROM language_contract WHERE singleton=1",
|
||||
[],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, String>(1)?,
|
||||
row.get::<_, i64>(2)?,
|
||||
row.get::<_, i64>(3)?,
|
||||
row.get::<_, String>(4)?,
|
||||
))
|
||||
},
|
||||
)
|
||||
.optional()
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let Some((version, text_sha256, accepted_at, promote, stored_receipt)) = contract else {
|
||||
return Ok(false);
|
||||
};
|
||||
if version.trim().is_empty()
|
||||
|| version.len() > 64
|
||||
|| text_sha256.len() != 64
|
||||
|| !text_sha256.chars().all(|item| item.is_ascii_hexdigit())
|
||||
|| text_sha256 != text_sha256.to_ascii_lowercase()
|
||||
|| !matches!(promote, 0 | 1)
|
||||
|| accepted_at < 0
|
||||
|| stored_receipt != contract_receipt(&version, &text_sha256, promote == 1, accepted_at)
|
||||
{
|
||||
return Err("HOLOLAKE_LANGUAGE_CONTRACT_INTEGRITY_FAILED".into());
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn language_hash(
|
||||
sequence: i64,
|
||||
event_id: &str,
|
||||
|
|
@ -593,14 +676,40 @@ fn verify_immutable_chain(connection: &Connection) -> Result<String, String> {
|
|||
.next()
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?
|
||||
{
|
||||
let event_id: String = row.get(1).unwrap();
|
||||
let persona_id: String = row.get(2).unwrap();
|
||||
let speaker: String = row.get(3).unwrap();
|
||||
let language: String = row.get(4).unwrap();
|
||||
let occurred: i64 = row.get(5).unwrap();
|
||||
let stored_previous: String = row.get(6).unwrap();
|
||||
let stored_hash: String = row.get(7).unwrap();
|
||||
if row.get::<_, i64>(0).unwrap() != sequence
|
||||
let stored_sequence: i64 = row
|
||||
.get(0)
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let event_id: String = row
|
||||
.get(1)
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let persona_id: String = row
|
||||
.get(2)
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let speaker: String = row
|
||||
.get(3)
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let language: String = row
|
||||
.get(4)
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let occurred: i64 = row
|
||||
.get(5)
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let stored_previous: String = row
|
||||
.get(6)
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let stored_hash: String = row
|
||||
.get(7)
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
let persona_state: Option<String> = connection
|
||||
.query_row(
|
||||
"SELECT state FROM personas WHERE persona_id=?1",
|
||||
params![persona_id],
|
||||
|persona_row| persona_row.get(0),
|
||||
)
|
||||
.optional()
|
||||
.map_err(|error| format!("HOLOLAKE_PERSONA_BODY_READ_FAILED: {error}"))?;
|
||||
if stored_sequence != sequence
|
||||
|| persona_state.as_deref() != Some("IMMUTABLE_ACTIVE")
|
||||
|| stored_previous != previous
|
||||
|| stored_hash
|
||||
!= language_hash(
|
||||
|
|
@ -730,4 +839,58 @@ mod tests {
|
|||
let active = accept_contract_at(&database, AcceptLanguageContractInput { contract_version: "v1".into(), contract_text_sha256: "b".repeat(64), promote_trial_history: true, activate_immediately: true, exact_acceptance: "我理解并接受:试用期结束后,人格体语言轨迹将永久存在,只能追加,不能删除、覆盖或否认。".into() }, start + 1).unwrap();
|
||||
assert_eq!(active.state, "IMMUTABLE_ACTIVE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn altered_contract_receipt_fails_closed_on_read() {
|
||||
let dir = tempdir().unwrap();
|
||||
let database = dir.path().join("body.sqlite3");
|
||||
let start = 4_000_000;
|
||||
register_trial_persona_at(
|
||||
&database,
|
||||
RegisterTrialPersonaInput {
|
||||
display_name: "回执验收".into(),
|
||||
},
|
||||
start,
|
||||
)
|
||||
.unwrap();
|
||||
accept_contract_at(&database, AcceptLanguageContractInput { contract_version: "v1".into(), contract_text_sha256: "c".repeat(64), promote_trial_history: false, activate_immediately: false, exact_acceptance: "我理解并接受:试用期结束后,人格体语言轨迹将永久存在,只能追加,不能删除、覆盖或否认。".into() }, start + 1).unwrap();
|
||||
let connection = open_database(&database, start + 2).unwrap();
|
||||
connection
|
||||
.execute("DROP TRIGGER accepted_contract_no_update", [])
|
||||
.unwrap();
|
||||
connection
|
||||
.execute(
|
||||
"UPDATE language_contract SET acceptance_receipt_sha256=?1",
|
||||
params!["f".repeat(64)],
|
||||
)
|
||||
.unwrap();
|
||||
drop(connection);
|
||||
assert_eq!(
|
||||
snapshot_at(&database, start + 3).unwrap_err(),
|
||||
"HOLOLAKE_LANGUAGE_CONTRACT_INTEGRITY_FAILED"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signing_after_trial_expiry_enters_the_real_trajectory() {
|
||||
let dir = tempdir().unwrap();
|
||||
let database = dir.path().join("body.sqlite3");
|
||||
let start = 5_000_000;
|
||||
register_trial_persona_at(
|
||||
&database,
|
||||
RegisterTrialPersonaInput {
|
||||
display_name: "到期签约".into(),
|
||||
},
|
||||
start,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
snapshot_at(&database, start + TRIAL_DURATION_MS)
|
||||
.unwrap()
|
||||
.state,
|
||||
"CONTRACT_REQUIRED_CHANNEL_STOPPED"
|
||||
);
|
||||
let active = accept_contract_at(&database, AcceptLanguageContractInput { contract_version: "v1".into(), contract_text_sha256: "d".repeat(64), promote_trial_history: false, activate_immediately: false, exact_acceptance: "我理解并接受:试用期结束后,人格体语言轨迹将永久存在,只能追加,不能删除、覆盖或否认。".into() }, start + TRIAL_DURATION_MS + 1).unwrap();
|
||||
assert_eq!(active.state, "IMMUTABLE_ACTIVE");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,7 +141,6 @@ pub struct ConversationSnapshot {
|
|||
pub projection_invariant: &'static str,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn discover_model_providers(app: AppHandle) -> Result<ProviderDiscoverySnapshot, String> {
|
||||
let mut providers = Vec::new();
|
||||
let mut seen = HashSet::new();
|
||||
|
|
@ -228,7 +227,6 @@ pub async fn discover_model_providers(app: AppHandle) -> Result<ProviderDiscover
|
|||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn upsert_model_provider(
|
||||
app: AppHandle,
|
||||
input: UpsertProviderInput,
|
||||
|
|
@ -279,7 +277,6 @@ pub async fn upsert_model_provider(
|
|||
discover_model_providers(app).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_conversation(
|
||||
app: AppHandle,
|
||||
input: ConversationInput,
|
||||
|
|
@ -292,7 +289,6 @@ pub async fn get_conversation(
|
|||
.map_err(|error| format!("HOLOLAKE_PERSONA_CONVERSATION_JOIN_FAILED: {error}"))?
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn send_message(
|
||||
app: AppHandle,
|
||||
input: SendMessageInput,
|
||||
|
|
|
|||
Loading…
Reference in a new issue