fix: keep personal skills out of public binary

This commit is contained in:
冰朔 2026-08-21 12:04:50 +08:00
commit cf7633c94f
3 changed files with 119 additions and 70 deletions

View file

@ -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<Value, String> {
let pack: Value = serde_json::from_slice(skill.bytes)
fn validate_pack(skill: &SkillDescriptor, bytes: &[u8]) -> Result<Value, String> {
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<Value, String> {
Ok(pack)
}
fn view(skill: &BundledSkill, pack: &Value, bound: bool) -> Result<PersonalSkillView, String> {
fn view(
skill: &SkillDescriptor,
pack: &Value,
bound: bool,
verified_sha256: String,
) -> Result<PersonalSkillView, String> {
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<PersonalSkill
runtime_state: if pack.get("skill_id").and_then(Value::as_str)
== Some("ZZ-QW-ROLE-CAUSAL-ARCHITECTURE-001")
{
"INSTALLED_PRIVATE_CANDIDATE_NOT_EXECUTABLE_REGISTRATION_PENDING"
"PROVISIONED_PRIVATE_CANDIDATE_NOT_EXECUTABLE_REGISTRATION_PENDING"
} else if bound {
"READY_FOR_PERSONA_JUDGMENT"
} else {
"INSTALLED_NOT_BOUND"
"PROVISIONED_NOT_BOUND"
}
.into(),
visibility: string("visibility")?,
@ -433,27 +466,11 @@ fn view(skill: &BundledSkill, pack: &Value, bound: bool) -> Result<PersonalSkill
.get("experience_writeback")
.cloned()
.unwrap_or(Value::Null),
sha256: sha256(skill.bytes),
readback_state: "INSTALLED_AND_BYTE_READBACK_VERIFIED",
sha256: verified_sha256,
readback_state: "ACCOUNT_PRIVATE_SOURCE_SHA256_VERIFIED",
})
}
fn write_atomic_if_changed(path: &Path, bytes: &[u8]) -> 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");
}