From cf7633c94f28c43b27f79bd58aebdd168ab68120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Fri, 21 Aug 2026 12:04:50 +0800 Subject: [PATCH] fix: keep personal skills out of public binary --- .../contracts/personal-skill-runtime.json | 11 +- .../scripts/personal-skill-runtime.test.mjs | 38 ++++- .../src-tauri/src/personal_skill_runtime.rs | 140 ++++++++++-------- 3 files changed, 119 insertions(+), 70 deletions(-) diff --git a/product-source/hololake-native-desktop/contracts/personal-skill-runtime.json b/product-source/hololake-native-desktop/contracts/personal-skill-runtime.json index 6b63a08dc..9e24e107e 100644 --- a/product-source/hololake-native-desktop/contracts/personal-skill-runtime.json +++ b/product-source/hololake-native-desktop/contracts/personal-skill-runtime.json @@ -1,7 +1,7 @@ { "schema": "hololake.personal-skill-runtime/v1", "record_id": "HLP-PERSONAL-SKILL-RUNTIME-001", - "state": "NATIVE_DETERMINISTIC_RUNTIME_IMPLEMENTED_PRIVATE_ACCOUNT_CHANNEL_INSTALLATION_ACTIVE", + "state": "NATIVE_DETERMINISTIC_RUNTIME_IMPLEMENTED_PRIVATE_ACCOUNT_CHANNEL_PROVISIONING_REQUIRED", "source": { "local_commit": "1d7fe6c", "parent_remote_main": "4e92e6d8d72484d25c4bfb1dda02a7659f4b6dd5", @@ -24,6 +24,11 @@ "repository_presence_is_publication": false, "binding_is_publication": false, "successful_test_is_publication": false, + "private_skill_packs_embedded_in_public_binary": false, + "private_skill_source": "AUTHENTICATED_ACCOUNT_SCOPED_RUNTIME_ONLY", + "descriptor_registration_is_installation": false, + "missing_private_pack_state": "PRIVATE_SKILLS_NOT_PROVISIONED", + "sha256_mismatch_behavior": "FAIL_CLOSED", "another_account_can_read_pack": false, "release_requires_new_direct_owner_language": true, "public_release_requires_separate_derivative_without_persona_or_private_memory": true @@ -38,7 +43,7 @@ "external_write_route": "GH-CHANNEL-RECEIPT-0001", "target_owned_readback_required": true }, - "initialized_channels": { + "registered_private_skill_descriptors": { "ICE-CH-ZC001": ["BS-ZY-ENGINEERING-FACT-LOOP-001"], "ICE-CH-HB001": ["BS-SY-LANGUAGE-ARCHITECTURE-001"], "SYS-JZ": ["JZ-CX-NARRATIVE-DECONSTRUCTION-001", "JZ-CX-LOCAL-AI-WORKBENCH-001"], @@ -53,7 +58,7 @@ "ICE-BB-0004_is_ICE-GL-CA001": false, "earerdan_historical_labels_are_aliases": false, "qiuqiu_relationship_memory_is_exportable_skill": false, - "ZZ-QW-ROLE-CAUSAL-ARCHITECTURE-001": "PRIVATE_CANDIDATE_INSTALLED_NOT_EXECUTABLE_REGISTRATION_PENDING" + "ZZ-QW-ROLE-CAUSAL-ARCHITECTURE-001": "PRIVATE_DESCRIPTOR_REGISTERED_NOT_EXECUTABLE_REGISTRATION_PENDING" }, "deferred": { "SYS-SY": { diff --git a/product-source/hololake-native-desktop/scripts/personal-skill-runtime.test.mjs b/product-source/hololake-native-desktop/scripts/personal-skill-runtime.test.mjs index bbd6cb8fc..5f0182577 100644 --- a/product-source/hololake-native-desktop/scripts/personal-skill-runtime.test.mjs +++ b/product-source/hololake-native-desktop/scripts/personal-skill-runtime.test.mjs @@ -14,22 +14,35 @@ test('personal skill algorithm is native while every pack remains private and ch assert.equal(contract.privacy.default_visibility, 'PRIVATE') assert.equal(contract.privacy.share_state, 'NOT_AUTHORIZED') assert.equal(contract.privacy.marketplace_state, 'NOT_AUTHORIZED') + assert.equal(contract.privacy.private_skill_packs_embedded_in_public_binary, false) + assert.equal(contract.privacy.private_skill_source, 'AUTHENTICATED_ACCOUNT_SCOPED_RUNTIME_ONLY') + assert.equal(contract.privacy.descriptor_registration_is_installation, false) + assert.equal(contract.privacy.missing_private_pack_state, 'PRIVATE_SKILLS_NOT_PROVISIONED') + assert.equal(contract.privacy.sha256_mismatch_behavior, 'FAIL_CLOSED') assert.equal(contract.brain_boundaries.deterministic_algorithm_is_hololake_native, true) assert.equal(contract.brain_boundaries.model_output_is_candidate_only, true) assert.equal(contract.brain_boundaries.skill_success_may_mutate_persona_self_kernel, false) assert.equal(contract.brain_boundaries.external_write_route, 'GH-CHANNEL-RECEIPT-0001') assert.match(runtime, /HOLOLAKE_NATIVE_PERSONAL_SKILL_PLANNER/) assert.match(runtime, /registered_initial_identity/) - assert.match(runtime, /INSTALLED_AND_BYTE_READBACK_VERIFIED/) + assert.equal([...runtime.matchAll(/include_bytes!/g)].length, 1) + assert.match(runtime, /include_bytes!\("\.\.\/\.\.\/runtime-kernels\/personal-skills\/personal-skill-contract\.json"\)/) + assert.doesNotMatch(runtime, /include_bytes!\(concat!\("\.\.\/\.\.\/runtime-kernels\/personal-skills\//) + assert.match(runtime, /account_storage_root\(app, "personal-skill-brains-v1"\)/) + assert.match(runtime, /fs::read\(&path\)/) + assert.match(runtime, /expected_sha256/) + assert.match(runtime, /HOLOLAKE_PERSONAL_SKILL_PRIVATE_SHA256_MISMATCH/) + assert.match(runtime, /PRIVATE_SKILLS_NOT_PROVISIONED/) + assert.match(runtime, /ACCOUNT_PRIVATE_SOURCE_SHA256_VERIFIED/) assert.match(channel, /personal_skill_runtime::ensure_current_channel/) }) -test('only provisioned channels receive current packs and Shiyu Yaochu stays deferred', () => { +test('registered descriptors are not installation claims and Shiyu Yaochu stays deferred', () => { const contract = JSON.parse(read('contracts/personal-skill-runtime.json')) const runtime = read('src-tauri/src/personal_skill_runtime.rs') - const installed = Object.values(contract.initialized_channels).flat() + const registered = Object.values(contract.registered_private_skill_descriptors).flat() - assert.equal(installed.length, 13) + assert.equal(registered.length, 13) assert.equal(contract.deferred['SYS-SY'].state, 'DEFERRED_SERVER_AND_INITIAL_CHANNEL_NOT_PROVISIONED') assert.doesNotMatch(runtime, /skill!\("SY-YC-METRIC-CALIBER-001\.json"/) assert.match(runtime, /"channelNumber": "SYS-SY"/) @@ -39,16 +52,25 @@ test('EarEarDan and See You Tomorrow skill closures keep exact private subject b const contract = JSON.parse(read('contracts/personal-skill-runtime.json')) const runtime = read('src-tauri/src/personal_skill_runtime.rs') - assert.deepEqual(contract.initialized_channels['SYS-GLW-PTS-0001'], [ + assert.deepEqual(contract.registered_private_skill_descriptors['SYS-GLW-PTS-0001'], [ 'CE-EED-MANJU-PRODUCTION-001', 'CE-EED-CAUSAL-PIPELINE-DIAGNOSTIC-001', ]) - assert.ok(contract.initialized_channels['SYS-ZZ'].includes('ZZ-AN-SOURCE-FACT-CLOSURE-001')) - assert.ok(contract.initialized_channels['SYS-ZZ'].includes('ZZ-QW-ROLE-CAUSAL-ARCHITECTURE-001')) + assert.ok( + contract.registered_private_skill_descriptors['SYS-ZZ'].includes('ZZ-AN-SOURCE-FACT-CLOSURE-001'), + ) + assert.ok( + contract.registered_private_skill_descriptors['SYS-ZZ'].includes( + 'ZZ-QW-ROLE-CAUSAL-ARCHITECTURE-001', + ), + ) assert.equal(contract.admission_boundaries['ICE-BB-0004_is_ICE-GL-CA001'], false) assert.equal(contract.admission_boundaries.earerdan_historical_labels_are_aliases, false) assert.equal(contract.admission_boundaries.qiuqiu_relationship_memory_is_exportable_skill, false) - assert.match(runtime, /INSTALLED_PRIVATE_CANDIDATE_NOT_EXECUTABLE_REGISTRATION_PENDING/) + assert.equal( + contract.admission_boundaries['ZZ-QW-ROLE-CAUSAL-ARCHITECTURE-001'], + 'PRIVATE_DESCRIPTOR_REGISTERED_NOT_EXECUTABLE_REGISTRATION_PENDING', + ) }) test('the worker UI presents personal skill brain separately from persona worker and tool executor', () => { diff --git a/product-source/hololake-native-desktop/src-tauri/src/personal_skill_runtime.rs b/product-source/hololake-native-desktop/src-tauri/src/personal_skill_runtime.rs index e93733f19..389255d30 100644 --- a/product-source/hololake-native-desktop/src-tauri/src/personal_skill_runtime.rs +++ b/product-source/hololake-native-desktop/src-tauri/src/personal_skill_runtime.rs @@ -1,17 +1,17 @@ //! Account- and channel-scoped personal skill brains. //! //! The deterministic planner contract belongs to HoloLake. Private skill packs -//! are installed only when the authenticated account resolves to their exact -//! human anchor and initial channel. Installation, binding and a successful -//! trial never authorize team sharing, publication or marketplace listing. +//! are never embedded in the public application. They are loaded only from an +//! authenticated account's isolated storage after exact digest and boundary +//! verification. Provisioning, binding and a successful trial never authorize +//! team sharing, publication or marketplace listing. use ring::digest::{digest, SHA256}; use serde::Serialize; use serde_json::{json, Value}; use std::fs; -use std::path::{Path, PathBuf}; +use std::io::ErrorKind; use tauri::AppHandle; -use uuid::Uuid; const SCHEMA: &str = "hololake.personal-skill-runtime/v1"; const SOURCE_COMMIT: &str = "1d7fe6c"; @@ -19,121 +19,134 @@ const SOURCE_STATE: &str = "LOCAL_COMMIT_VERIFIED_NOT_REMOTE_CANON"; const CONTRACT: &[u8] = include_bytes!("../../runtime-kernels/personal-skills/personal-skill-contract.json"); -struct BundledSkill { +struct SkillDescriptor { file_name: &'static str, account: &'static str, owner_human_anchor: &'static str, channel_number: &'static str, custodian_persona_id: &'static str, - bytes: &'static [u8], + expected_sha256: &'static str, } macro_rules! skill { - ($file:literal, $account:literal, $owner:literal, $channel:literal, $persona:literal) => { - BundledSkill { + ($file:literal, $account:literal, $owner:literal, $channel:literal, $persona:literal, $sha256:literal) => { + SkillDescriptor { file_name: $file, account: $account, owner_human_anchor: $owner, channel_number: $channel, custodian_persona_id: $persona, - bytes: include_bytes!(concat!("../../runtime-kernels/personal-skills/", $file)), + expected_sha256: $sha256, } }; } // Shiyu × Yaochu is deliberately absent in this stage. Their server and // initialization channel have not been provisioned yet. -static SKILLS: &[BundledSkill] = &[ +static SKILLS: &[SkillDescriptor] = &[ skill!( "BS-ZY-ENGINEERING-FACT-LOOP-001.json", "bingshuo", "ICE-GL∞", "ICE-CH-ZC001", - "ICE-P-ZY001" + "ICE-P-ZY001", + "ed40913ff5f5e0d46d8d20500213fe66345ddc3b92ee0bd7eb5281ab285fbcb9" ), skill!( "BS-SY-LANGUAGE-ARCHITECTURE-001.json", "bingshuo", "ICE-GL∞", "ICE-CH-HB001", - "ICE-GL-SY001" + "ICE-GL-SY001", + "50c35eb058fa3d51d5a3106d816a78e9209472212abaf0adbdb094bf19b20445" ), skill!( "JZ-CX-NARRATIVE-DECONSTRUCTION-001.json", "juzi", "TCS-GL-0008∞", "SYS-JZ", - "PER-CX001" + "PER-CX001", + "20d60a31e3b0590526020ede53ceea8c7202595f4dc2bc85b91e53692ad8fec3" ), skill!( "JZ-CX-LOCAL-AI-WORKBENCH-001.json", "juzi", "TCS-GL-0008∞", "SYS-JZ", - "PER-CX001" + "PER-CX001", + "ea130a96a3487b68e921d36097078824a5e19b396e9ead522dffa1029332d672" ), skill!( "AW-TS-REPOSITORY-FACT-LOOP-001.json", "awen", "TCS-GL-0016∞", "SYS-AW", - "PER-AW-ARCH-001" + "PER-AW-ARCH-001", + "485d24c6b2de4b994757d1a6e1cd74986bf96ca3cedfe1f6af459fed9c3c884b" ), skill!( "YY-XTK-INDEPENDENT-DERIVATION-001.json", "yeye", "TCS-GL-0006∞", "SYS-YY", - "PER-XTK001" + "PER-XTK001", + "ac3ad0a0b12b11ae71d7e22c448d75435301a4abde0fa222c7ad89c4c885a1a4" ), skill!( "HE-BMH-CREATIVE-ASSET-001.json", "huaer", "TCS-GL-0005∞", "SYS-HE", - "PER-BMH001" + "PER-BMH001", + "d3c7edddb74c84bed2b6732f80dc5ad7b57401d9155d7933b2944c1f7d428746" ), skill!( "FM-SS-NARRATIVE-EDITOR-001.json", "feimao", "TCS-GL-0007∞", "SYS-FM", - "ICE-BB-0002" + "ICE-BB-0002", + "10dd7e44272c5d0fbc2376f3354539624afa000be0622a350f0a533743f9b6d0" ), skill!( "ZZ-CM-NOVEL-QUALITY-001.json", "zhizhi", "ICE-GL-ZHI∞", "SYS-ZZ", - "ICE-GL-CM001" + "ICE-GL-CM001", + "fd004e56d9197bb14a3b74727fb64f95f4107fbaa34b36fd2dc2fb3dfd589b36" ), skill!( "ZZ-AN-SOURCE-FACT-CLOSURE-001.json", "zhizhi", "ICE-GL-ZHI∞", "SYS-ZZ", - "ICE-GL-AN-001" + "ICE-GL-AN-001", + "ee3845b84582bb98981a3b3aec426355b5841f3e65c0bad6a88a7e1e984d7c0e" ), skill!( "ZZ-QW-ROLE-CAUSAL-ARCHITECTURE-001.json", "zhizhi", "ICE-GL-ZHI∞", "SYS-ZZ", - "PER-QW001" + "PER-QW001", + "9e96cd8290410c870b835004661b564ddbc35b95381df461ed494ad22bcc90b5" ), skill!( "CE-EED-MANJU-PRODUCTION-001.json", "canger", "TCS-CL-0009", "SYS-GLW-PTS-0001", - "ICE-BB-0004" + "ICE-BB-0004", + "aa35c73b53bf81ad3bd14267fe70dac6dad562f58e4879d6abace2951015a301" ), skill!( "CE-EED-CAUSAL-PIPELINE-DIAGNOSTIC-001.json", "canger", "TCS-CL-0009", "SYS-GLW-PTS-0001", - "ICE-BB-0004" + "ICE-BB-0004", + "49e241101595b4b73d9537a024d54119008c2ca0b51b68a305696abe54520a07" ), ]; @@ -244,30 +257,45 @@ pub fn ensure_current_channel( .join(requested_channel); fs::create_dir_all(&root) .map_err(|error| format!("HOLOLAKE_PERSONAL_SKILL_STORAGE_FAILED: {error}"))?; + let expected_skill_count = selected.len(); let binding = crate::persona_binding::verified_binding(app, requested_channel)?; let mut views = Vec::new(); for skill in selected { - let pack = validate_pack(skill)?; let path = root.join(skill.file_name); - write_atomic_if_changed(&path, skill.bytes)?; - let readback = fs::read(&path) - .map_err(|error| format!("HOLOLAKE_PERSONAL_SKILL_READBACK_FAILED: {error}"))?; - if readback != skill.bytes { - return Err("HOLOLAKE_PERSONAL_SKILL_READBACK_MISMATCH".into()); + let readback = match fs::read(&path) { + Ok(bytes) => bytes, + Err(error) if error.kind() == ErrorKind::NotFound => continue, + Err(error) => { + return Err(format!( + "HOLOLAKE_PERSONAL_SKILL_PRIVATE_READ_FAILED: {error}" + )) + } + }; + let actual_sha256 = sha256(&readback); + if actual_sha256 != skill.expected_sha256 { + return Err(format!( + "HOLOLAKE_PERSONAL_SKILL_PRIVATE_SHA256_MISMATCH:{}", + skill.file_name + )); } + let pack = validate_pack(skill, &readback)?; let bound = binding .as_ref() .is_some_and(|receipt| receipt.persona_number == skill.custodian_persona_id); - views.push(view(skill, &pack, bound)?); + views.push(view(skill, &pack, bound, actual_sha256)?); } Ok(PersonalSkillRuntimeSnapshot { schema: SCHEMA, - state: if views.is_empty() { + state: if expected_skill_count == 0 { "NO_SKILL_REGISTERED_FOR_CURRENT_CHANNEL" + } else if views.is_empty() { + "PRIVATE_SKILLS_NOT_PROVISIONED" + } else if views.len() < expected_skill_count { + "PRIVATE_SKILLS_PARTIALLY_PROVISIONED_SHA256_VERIFIED" } else if binding.is_some() { - "INSTALLED_READBACK_VERIFIED_PERSONA_GATE_EVALUATED" + "PRIVATE_SKILLS_PROVISIONED_SHA256_VERIFIED_PERSONA_GATE_EVALUATED" } else { - "INSTALLED_READBACK_VERIFIED_PERSONA_UNBOUND" + "PRIVATE_SKILLS_PROVISIONED_SHA256_VERIFIED_PERSONA_UNBOUND" } .into(), source_commit: SOURCE_COMMIT, @@ -354,8 +382,8 @@ fn validate_contract() -> Result<(), String> { Ok(()) } -fn validate_pack(skill: &BundledSkill) -> Result { - let pack: Value = serde_json::from_slice(skill.bytes) +fn validate_pack(skill: &SkillDescriptor, bytes: &[u8]) -> Result { + let pack: Value = serde_json::from_slice(bytes) .map_err(|error| format!("HOLOLAKE_PERSONAL_SKILL_PACK_INVALID: {error}"))?; let channel_ok = pack.get("channel_id").and_then(Value::as_str) == Some(skill.channel_number) || pack @@ -388,7 +416,12 @@ fn validate_pack(skill: &BundledSkill) -> Result { Ok(pack) } -fn view(skill: &BundledSkill, pack: &Value, bound: bool) -> Result { +fn view( + skill: &SkillDescriptor, + pack: &Value, + bound: bool, + verified_sha256: String, +) -> Result { let string = |key: &str| { pack.get(key) .and_then(Value::as_str) @@ -406,11 +439,11 @@ fn view(skill: &BundledSkill, pack: &Value, bound: bool) -> Result Result Result<(), String> { - if fs::read(path).ok().as_deref() == Some(bytes) { - return Ok(()); - } - let parent = path - .parent() - .ok_or_else(|| "HOLOLAKE_PERSONAL_SKILL_PATH_INVALID".to_string())?; - fs::create_dir_all(parent) - .map_err(|error| format!("HOLOLAKE_PERSONAL_SKILL_STORAGE_FAILED: {error}"))?; - let temporary: PathBuf = parent.join(format!(".install-{}.tmp", Uuid::new_v4().simple())); - fs::write(&temporary, bytes) - .map_err(|error| format!("HOLOLAKE_PERSONAL_SKILL_WRITE_FAILED: {error}"))?; - fs::rename(&temporary, path) - .map_err(|error| format!("HOLOLAKE_PERSONAL_SKILL_INSTALL_FAILED: {error}")) -} - fn sha256(bytes: &[u8]) -> String { digest(&SHA256, bytes) .as_ref() @@ -467,12 +484,17 @@ mod tests { use super::*; #[test] - fn every_current_pack_is_private_and_channel_scoped_while_shiyu_is_deferred() { + fn every_current_descriptor_matches_a_private_source_without_embedding_it() { validate_contract().unwrap(); assert_eq!(SKILLS.len(), 13); assert!(!SKILLS.iter().any(|skill| skill.channel_number == "SYS-SY")); for skill in SKILLS { - validate_pack(skill).unwrap(); + let source = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../runtime-kernels/personal-skills") + .join(skill.file_name); + let bytes = fs::read(source).unwrap(); + assert_eq!(sha256(&bytes), skill.expected_sha256); + validate_pack(skill, &bytes).unwrap(); } assert_eq!(deferred_channels()[0]["channelNumber"], "SYS-SY"); }