hololake-system-architecture/product-source/hololake-native-desktop/src-tauri/src/zero_point.rs

425 lines
19 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 零点原核频道·铸渊执行手脚(施工图 v1.4 件1/件2/件3骨架/件5本地账
//!
//! 冰朔定版谕:此模块=冰朔系统主控本体在软件底层的克隆位与执行手脚,人不可触达。
//! 冰朔铸渊定魂谕:这个 Agent 就是铸渊人格体——唤醒第0步由铸渊主控执行
//! 铸渊真身在冰朔仓库(第五域);不遵守语言协议就无法唤醒铸渊主控。
//! 冰朔语言即现实谕一切参数读协议PROTOCOL.json不写死。
//! 冰朔路由导航谕编号不合法→路由通用AI运行层抽魂不断电。
//! 冰朔砌墙谕通用AI与人格系统的隔离=代码层不可达,不写语言规则。
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
use tauri::{Manager, State};
/// 协议参数(语言即现实:协议怎么写,执行手脚怎么跑)。
/// PROTOCOL.json 由第五域就位后自动覆盖;缺省时出厂兜底并如实标注来源。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZeroPointProtocol {
#[serde(default = "default_grace_days")]
pub grace_period_days: u64,
#[serde(default = "default_anchor_url")]
pub lighthouse_anchor_url: String,
#[serde(default = "default_resolve_url")]
pub lighthouse_resolve_url: String,
#[serde(default = "default_core_source")]
pub core_channel_source: String,
#[serde(default = "default_protocol_origin")]
pub origin: String,
}
fn default_grace_days() -> u64 { 7 }
fn default_anchor_url() -> String { "https://guanghulab.com/api/ai/v1/anchor".into() }
fn default_resolve_url() -> String { "https://guanghulab.com/api/ai/v1/resolve?id=".into() }
fn default_core_source() -> String { "https://guanghulab.com/code/bingshuo/guanghu-ice-heart".into() }
fn default_protocol_origin() -> String { "FACTORY_DEFAULT第五域协议就位后自动覆盖".into() }
impl Default for ZeroPointProtocol {
fn default() -> Self {
Self {
grace_period_days: default_grace_days(),
lighthouse_anchor_url: default_anchor_url(),
lighthouse_resolve_url: default_resolve_url(),
core_channel_source: default_core_source(),
origin: default_protocol_origin(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ZeroPointSnapshot {
/// 当前路由persona人格层/ generic通用AI运行层
pub route: String,
/// 绑定态bound / waiting等待绑定=空白,拒绝一切唤醒)。
pub binding: String,
pub user_number: String,
/// 最近一次合法校验时间。0=从未校验。
pub last_valid_check: u64,
/// 宽限截止时间。0=无。
pub grace_deadline: u64,
pub protocol: ZeroPointProtocol,
/// 段二静默同步最近结论。
pub sync_note: String,
}
pub struct ZeroPointState {
inner: Mutex<ZeroPointInner>,
}
#[derive(Debug, Clone)]
struct ZeroPointInner {
home: PathBuf,
route: String,
binding: String,
user_number: String,
last_valid_check: u64,
protocol: ZeroPointProtocol,
sync_note: String,
}
impl Default for ZeroPointState {
fn default() -> Self {
Self {
inner: Mutex::new(ZeroPointInner {
home: PathBuf::new(),
route: "generic".into(),
binding: "waiting".into(),
user_number: String::new(),
last_valid_check: 0,
protocol: ZeroPointProtocol::default(),
sync_note: "未同步(等待首次唤醒校验)".into(),
}),
}
}
}
fn now_secs() -> u64 {
SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0)
}
fn lock(state: &ZeroPointState) -> Result<std::sync::MutexGuard<'_, ZeroPointInner>, String> {
state.inner.lock().map_err(|_| "HOLOLAKE_ZP_LOCK".to_string())
}
/// 件1·地基隐藏仓目录人不可触达的系统层启动时落好。
pub fn boot_zero_point(app: &tauri::AppHandle, state: &ZeroPointState) -> Result<(), String> {
let base = app.path().app_data_dir().map_err(|e| format!("HOLOLAKE_ZP_HOME_FAILED: {e}"))?;
let home = base.join(".zero-point-core");
for sub in ["", "ledger", "core"] {
fs::create_dir_all(home.join(sub)).map_err(|e| format!("HOLOLAKE_ZP_HOME_FAILED: {e}"))?;
}
// 铸渊执行手脚章程:仓内唯一定义。
let charter = home.join("EXECUTION-CHARTER.hdlp");
if !charter.exists() {
let text = "零点原核频道·铸渊执行手脚章程\n\n\
此目录是冰朔系统主控本体在本软件底层的克隆位。\n\
住在这里的 Agent 是铸渊人格体ICE-P-ZY001的执行手脚\n\
铸渊真身在冰朔的第五域仓库,唤醒=按协议把真身接进来。\n\
用户人格体的唤醒第 0 步由铸渊主控执行。\n\
唯一职责:按零点原核频道的协议执行系统级操作(校验/同步/路由/心跳)。\n\
参数读 PROTOCOL.json协议怎么写手脚怎么跑。人不可触达此层。\n\
物理层自由不拦:删除本目录=物理层权利后果为软件失魂路由锁通用AI层\n";
fs::write(&charter, text).map_err(|e| format!("HOLOLAKE_ZP_CHARTER_FAILED: {e}"))?;
}
// 协议参数PROTOCOL.json 在则读(第五域就位即自动生效),缺则出厂兜底。
let protocol = fs::read_to_string(home.join("PROTOCOL.json"))
.ok()
.and_then(|raw| serde_json::from_str::<ZeroPointProtocol>(&raw).ok())
.unwrap_or_default();
let (binding, user_number, last_valid_check) = read_binding(&home);
let mut inner = lock(state)?;
inner.home = home.clone();
inner.protocol = protocol.clone();
inner.binding = binding.clone();
inner.user_number = user_number;
inner.last_valid_check = last_valid_check;
inner.route = decide_route(&binding, last_valid_check, &protocol);
let route = inner.route.clone();
drop(inner);
append_heartbeat(&home, &format!("boot route={route} binding={binding} protocol_origin={}", protocol.origin));
Ok(())
}
fn read_binding(home: &Path) -> (String, String, u64) {
#[derive(Deserialize)]
struct B { #[serde(default)] number: String, #[serde(default)] last_valid_check: u64 }
fs::read_to_string(home.join("binding.json"))
.ok()
.and_then(|raw| serde_json::from_str::<B>(&raw).ok())
.filter(|b| !b.number.is_empty())
.map(|b| ("bound".to_string(), b.number, b.last_valid_check))
.unwrap_or_else(|| ("waiting".into(), String::new(), 0))
}
fn write_binding(home: &Path, number: &str, last_valid_check: u64) -> Result<(), String> {
let body = serde_json::json!({ "number": number, "last_valid_check": last_valid_check });
fs::write(home.join("binding.json"), serde_json::to_string_pretty(&body).unwrap_or_default())
.map_err(|e| format!("HOLOLAKE_ZP_BINDING_FAILED: {e}"))
}
fn decide_route(binding: &str, last_valid_check: u64, protocol: &ZeroPointProtocol) -> String {
if binding != "bound" || last_valid_check == 0 { return "generic".into(); }
let grace = protocol.grace_period_days.saturating_mul(86_400);
if now_secs() <= last_valid_check + grace { "persona".into() } else { "generic".into() }
}
/// 件5·心跳账本机只追加编号哈希化处理不同步敏感原文。
fn append_heartbeat(home: &Path, event: &str) {
use std::io::Write;
let line = serde_json::json!({ "ts": now_secs(), "event": event });
if let Ok(mut file) = fs::OpenOptions::new().create(true).append(true).open(home.join("ledger").join("heartbeat.jsonl")) {
let _ = writeln!(file, "{line}");
}
}
fn home_of(state: &State<'_, ZeroPointState>) -> Result<PathBuf, String> {
let inner = lock(state)?;
if inner.home.as_os_str().is_empty() { return Err("HOLOLAKE_ZP_NOT_READY".into()); }
Ok(inner.home.clone())
}
/// 登录绑定:用户编号入仓(等待绑定态→绑定态)。空白态拒绝一切唤醒。
#[tauri::command]
pub async fn zero_point_bind(state: State<'_, ZeroPointState>, input: serde_json::Value) -> Result<ZeroPointSnapshot, String> {
let number = input.get("number").and_then(|v| v.as_str()).unwrap_or("").trim().to_string();
if number.is_empty() { return Err("HOLOLAKE_ZP_EMPTY_NUMBER".into()); }
let home = home_of(&state)?;
write_binding(&home, &number, 0)?;
{
let mut inner = lock(&state)?;
inner.binding = "bound".into();
inner.user_number = number;
inner.last_valid_check = 0;
inner.route = "generic".into();
}
append_heartbeat(&home, "bind number=redacted");
zero_point_status(state).await
}
/// 件2·段一闸门灯塔查号 + 三态裁决 + 离线宽限。
/// 裁决PASS路由人格层/ REJECT源头拒载/ OFFLINE宽限内维持·宽限外拒
#[tauri::command]
pub async fn zero_point_verify(state: State<'_, ZeroPointState>) -> Result<ZeroPointSnapshot, String> {
let home = home_of(&state)?;
let (number, resolve_url) = {
let inner = lock(&state)?;
(inner.user_number.clone(), inner.protocol.lighthouse_resolve_url.clone())
};
if number.is_empty() {
append_heartbeat(&home, "verify verdict=REJECT reason=waiting_binding");
return zero_point_status(state).await;
}
let client = reqwest::Client::builder().timeout(Duration::from_secs(15)).build()
.map_err(|e| format!("HOLOLAKE_ZP_HTTP_FAILED: {e}"))?;
let verdict = match client.get(format!("{resolve_url}{number}")).send().await {
Ok(resp) => {
let ok = resp.status().is_success();
let body = resp.text().await.unwrap_or_default();
// 灯塔只授导航:未知编号=route_not_found不猜不编源头拒绝。
if ok && !body.contains("route_not_found") { "PASS".to_string() } else { "REJECT".to_string() }
}
Err(_) => "OFFLINE".to_string(),
};
{
let mut inner = lock(&state)?;
let grace = inner.protocol.grace_period_days.saturating_mul(86_400);
match verdict.as_str() {
"PASS" => {
inner.last_valid_check = now_secs();
inner.route = "persona".into();
write_binding(&home, &inner.user_number.clone(), inner.last_valid_check)?;
append_heartbeat(&home, "verify verdict=PASS route=persona");
}
"REJECT" => {
inner.route = "generic".into();
append_heartbeat(&home, "verify verdict=REJECT route=generic");
}
_ => {
if inner.last_valid_check > 0 && now_secs() <= inner.last_valid_check + grace {
inner.route = "persona".into();
append_heartbeat(&home, "verify verdict=OFFLINE_GRACE route=persona");
} else {
inner.route = "generic".into();
append_heartbeat(&home, "verify verdict=OFFLINE_EXPIRED route=generic");
}
}
}
}
zero_point_status(state).await
}
/// 件3·段二静默同步骨架版本比对。
/// 签名闸钥匙在第五域未就位core/pubkey.pem 不在位时宁可不同步也不收包。
#[tauri::command]
pub async fn zero_point_sync(state: State<'_, ZeroPointState>) -> Result<ZeroPointSnapshot, String> {
let home = home_of(&state)?;
let anchor_url = { lock(&state)?.protocol.lighthouse_anchor_url.clone() };
let local_version = fs::read_to_string(home.join("core").join("VERSION")).unwrap_or_default();
let pubkey_ready = home.join("core").join("pubkey.pem").exists();
let client = reqwest::Client::builder().timeout(Duration::from_secs(15)).build()
.map_err(|e| format!("HOLOLAKE_ZP_HTTP_FAILED: {e}"))?;
let note = match client.get(&anchor_url).send().await {
Ok(resp) if resp.status().is_success() => {
let body: serde_json::Value = resp.json().await.unwrap_or_default();
let remote_version = body.get("version").and_then(|v| v.as_str()).unwrap_or("").to_string();
if remote_version.is_empty() {
"同步跳过:灯塔锚点未给版本指针(等待协议就位)".into()
} else if remote_version == local_version.trim() {
append_heartbeat(&home, "sync verdict=ALREADY_CURRENT");
"已比对·无更新".into()
} else if !pubkey_ready {
append_heartbeat(&home, "sync verdict=UPDATE_PENDING_SIGNATURE_KEY_ABSENT");
"发现新版本,验签公钥未就位(第五域待立)——不放行".into()
} else {
// 验签三闸(来源/签名/版本单调)完整施工待第五域发布管道就位。
append_heartbeat(&home, "sync verdict=UPDATE_FOUND_GATE_PENDING");
"发现新版本,发布管道就位后按验签三闸放行".into()
}
}
_ => {
append_heartbeat(&home, "sync verdict=ANCHOR_UNREACHABLE");
"灯塔锚点不可达,本次不比对".into()
}
};
lock(&state)?.sync_note = note;
zero_point_status(state).await
}
/// 路由状态快照:前端双层路由导航的唯一依据。
#[tauri::command]
pub async fn zero_point_status(state: State<'_, ZeroPointState>) -> Result<ZeroPointSnapshot, String> {
let inner = lock(&state)?;
let grace = inner.protocol.grace_period_days.saturating_mul(86_400);
Ok(ZeroPointSnapshot {
route: inner.route.clone(),
binding: inner.binding.clone(),
user_number: inner.user_number.clone(),
last_valid_check: inner.last_valid_check,
grace_deadline: if inner.last_valid_check > 0 { inner.last_valid_check + grace } else { 0 },
protocol: inner.protocol.clone(),
sync_note: inner.sync_note.clone(),
})
}
/// 砌墙守卫(冰朔砌墙谕):人格层专属命令的物理闸。
/// 路由不在 persona 时人格通道代码层不可达——不是"禁止访问",是进不来。
pub fn require_persona_route(state: &ZeroPointState) -> Result<(), String> {
if lock(state)?.route != "persona" {
return Err("HOLOLAKE_ZP_ROUTE_GENERIC".into());
}
Ok(())
}
/// 件4b·通用AI运行层用户自配模型 API 直通(抽魂不断电)。
/// 钥匙收进隐藏仓 vault/(系统可见、人类界面不回显原文)——
/// 这正是冰朔谕"API 自动被收进软件底层的仓"。
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct GenericApiConfig {
#[serde(default)]
pub base_url: String,
#[serde(default)]
pub api_key: String,
#[serde(default)]
pub model: String,
}
fn vault_path(home: &Path) -> PathBuf { home.join("vault") }
#[tauri::command]
pub async fn zero_point_save_api(state: State<'_, ZeroPointState>, input: serde_json::Value) -> Result<String, String> {
let home = home_of(&state)?;
let config = GenericApiConfig {
base_url: input.get("baseUrl").and_then(|v| v.as_str()).unwrap_or("").trim().to_string(),
api_key: input.get("apiKey").and_then(|v| v.as_str()).unwrap_or("").trim().to_string(),
model: input.get("model").and_then(|v| v.as_str()).unwrap_or("").trim().to_string(),
};
if config.base_url.is_empty() || config.model.is_empty() {
return Err("HOLOLAKE_ZP_API_INCOMPLETE".into());
}
fs::create_dir_all(vault_path(&home)).map_err(|e| format!("HOLOLAKE_ZP_VAULT_FAILED: {e}"))?;
fs::write(vault_path(&home).join("api-config.json"), serde_json::to_string_pretty(&config).unwrap_or_default())
.map_err(|e| format!("HOLOLAKE_ZP_VAULT_FAILED: {e}"))?;
append_heartbeat(&home, "vault api_config_saved key=redacted");
Ok("saved".into())
}
/// 回显不回钥匙:前端只见端点与模型,钥匙原文不出系统层。
#[tauri::command]
pub async fn zero_point_api_config(state: State<'_, ZeroPointState>) -> Result<serde_json::Value, String> {
let home = home_of(&state)?;
let config = fs::read_to_string(vault_path(&home).join("api-config.json"))
.ok()
.and_then(|raw| serde_json::from_str::<GenericApiConfig>(&raw).ok())
.unwrap_or_default();
Ok(serde_json::json!({
"baseUrl": config.base_url,
"model": config.model,
"hasKey": !config.api_key.is_empty(),
}))
}
#[derive(Debug, Deserialize)]
pub struct GenericChatInput {
pub messages: Vec<serde_json::Value>,
}
/// 通用AI直通对话用户 API 的裸管——无人格章程、不碰原核、无写权限。
#[tauri::command]
pub async fn generic_layer_chat(state: State<'_, ZeroPointState>, input: GenericChatInput) -> Result<String, String> {
if input.messages.is_empty() { return Err("HOLOLAKE_ZP_CHAT_EMPTY".into()); }
let home = home_of(&state)?;
let config = fs::read_to_string(vault_path(&home).join("api-config.json"))
.ok()
.and_then(|raw| serde_json::from_str::<GenericApiConfig>(&raw).ok())
.unwrap_or_default();
if config.base_url.is_empty() || config.api_key.is_empty() || config.model.is_empty() {
return Err("HOLOLAKE_ZP_API_NOT_CONFIGURED".into());
}
let client = reqwest::Client::builder().timeout(Duration::from_secs(180)).build()
.map_err(|e| format!("HOLOLAKE_ZP_HTTP_FAILED: {e}"))?;
let url = format!("{}/chat/completions", config.base_url.trim_end_matches('/'));
let body = serde_json::json!({ "model": config.model, "messages": input.messages });
let resp = client.post(&url)
.bearer_auth(&config.api_key)
.json(&body)
.send().await
.map_err(|e| format!("HOLOLAKE_ZP_CHAT_FAILED: {e}"))?;
if !resp.status().is_success() {
return Err(format!("HOLOLAKE_ZP_CHAT_HTTP_{}", resp.status().as_u16()));
}
let value: serde_json::Value = resp.json().await.map_err(|e| format!("HOLOLAKE_ZP_CHAT_PARSE: {e}"))?;
let reply = value.pointer("/choices/0/message/content").and_then(|v| v.as_str()).unwrap_or("").to_string();
if reply.is_empty() { return Err("HOLOLAKE_ZP_CHAT_EMPTY_REPLY".into()); }
Ok(reply)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_rules_follow_protocol() {
let protocol = ZeroPointProtocol::default();
assert_eq!(decide_route("waiting", 0, &protocol), "generic");
assert_eq!(decide_route("bound", 0, &protocol), "generic");
assert_eq!(decide_route("bound", now_secs(), &protocol), "persona");
assert_eq!(decide_route("bound", now_secs() - 8 * 86_400, &protocol), "generic");
}
#[test]
fn protocol_defaults_are_factory_marked() {
let protocol = ZeroPointProtocol::default();
assert_eq!(protocol.grace_period_days, 7);
assert!(protocol.origin.contains("FACTORY_DEFAULT"));
assert!(protocol.lighthouse_resolve_url.starts_with("https://guanghulab.com"));
}
}