M9:桌面壳与收口——镜匣最小壳、器谱登记、发布证据落位、重写计划功成身退

- 镜匣 desktop/(Tauri v2 最小壳):WebView 承载琉璃镜(frontend/out 或 devUrl);能力桥最小命令(通知);乾坤通道 HMAC 短期凭证构造(与 sandbox/src/auth.rs 验签同算法,密钥仅经 SANDBOX_AUTH_SECRET 环境注入——二进制零平台密钥,静态审计钉死);hard_stop/resume 本地硬急停原子断路(全部通道命令过 ensure_not_halted,本地最高优先级不经云端);tantivy 依赖与 capabilities/ 目录预留;updater 插件位预留;cargo check 通过
- 器谱域 domains/tianzai(迁移 010):devices/device_grants——一次配对(密钥指纹登记)、逐项授契(可授面显式枚举,未知能力键拒)、撤回立即生效、设备撤销连带全部授契失效且不可再授;/devices 器谱 API 组
- 发布收口:release-evidence.md 落位重写期 RW-* 证据包(M0–M9 十里程碑验收自动化套件与结果物);能力矩阵 M9 行收官;rewrite-plan.md 按计划删除(M0–M9 全部完成,能力矩阵与证据台账接棒),全库引用清理
- 验收(test_m9_jingxia 四用例):器谱未授契 403 留痕/撤回即效/设备撤销连带;镜匣零平台密钥(rg 审计源码与配置为空);硬急停语义钉死(通道命令全检查+resume 唯一恢复+HMAC 无密钥不出凭证)
- 五套件终检全绿:后端 349、前端 203、BFF 4、神识 5 包、镜匣 cargo check(2 个预存环境失败历次 HEAD 复验与本周期无关);Compose nginx profile 配置校验通过
This commit is contained in:
chenxin committed 2026-08-28 09:35:27 +08:00
1 parent c63b75152a
commit 2483a25f63
29 files changed
+10533 -179

No files matched your search

+5124
View File
File diff suppressed because it is too large. Load diff
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "jingxia"
version = "0.1.0"
description = "镜匣——魂镜最小桌面壳(WebView 承载琉璃镜 + 本地能力桥 + 乾坤通道)"
edition = "2021"
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# 乾坤通道:HMAC 授权构造(与 sandbox/src/auth.rs 同算法)
hmac = "0.12"
sha2 = "0.10"
base64 = "0.22"
# 本地全文检索预留(M9 只声明依赖与目录,不做实现)
tantivy = "0.22"
[features]
default = []
# updater 经 tauri-plugin-updater 插件接入(依赖平台签名配置,M9 只预留不启用)
+26
View File
@@ -0,0 +1,26 @@
# 镜匣 · 魂镜最小桌面壳(hunjing 桌面端)
> 文化命名「镜匣」(契约词典 §2):桌面 WebView 壳——承载琉璃镜、本地能力桥、乾坤通道与本地硬急停。
## M9 最小形态
- **WebView**:`tauri.conf.json` 指向 `../frontend/out`(构建产物)或开发服务器 `http://localhost:3000`;
- **能力桥**:`send_notification` 等最小命令;器谱逐项授契(灵台 `/devices` 器谱裁决),未授契能力被拒;
- **乾坤通道**:`qiankun_grant` 构造 HMAC 短期凭证(与 `sandbox/src/auth.rs` 验签同算法);密钥读自 `SANDBOX_AUTH_SECRET` 环境变量——**二进制零平台密钥**;
- **硬急停**:`hard_stop` / `resume`——本地原子开关,触发即断全部通道(不经云端,优先级最高);
- **tantivy 本地检索**:依赖已声明,`capabilities/` 目录预留,M9 不做实现。
## 验收(M9 发布门)
1. **零平台密钥**:`rg -i 'sk-|api[_-]?key\s*=' src/ tauri.conf.json capabilities/` 为空(密钥只经环境注入);
2. **未授契能力被拒**:乾坤凭证构造必须配密钥;器谱能力断言走灵台司命链(403 留痕);
3. **硬急停可用**:`hard_stop` 后 `qiankun_grant`/`send_notification` 全部拒绝,`resume` 显式恢复。
## 本地运行
```bash
# 前端构建产物(或使用 devUrl 指向开发服务器)
cd ../frontend && pnpm build
# 运行镜匣(乾坤通道需本地沙箱密钥;不配置时通道关闭,其余照常)
cd ../desktop && cargo run
```
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
+10
View File
@@ -0,0 +1,10 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "镜匣最小能力集:WebView + 窗口 + 能力桥命令;无平台密钥、无文件系统直写、无 shell 直执行",
"windows": ["main"],
"permissions": [
"core:default",
"core:window:default"
]
}
+1
View File
@@ -0,0 +1 @@
{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener","allow-supports-multiple-windows"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-supports-multiple-windows":{"identifier":"allow-supports-multiple-windows","description":"Enables the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":["supports_multiple_windows"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-reLine truncated
+1
View File
@@ -0,0 +1 @@
{"default":{"identifier":"default","description":"镜匣最小能力集:WebView + 窗口 + 能力桥命令;无平台密钥、无文件系统直写、无 shell 直执行","local":true,"windows":["main"],"permissions":["core:default","core:window:default"]}}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

+146
View File
@@ -0,0 +1,146 @@
// 镜匣——魂镜最小桌面壳(rewrite/1.0 M9)。
//
// 职责(service-boundaries.md §8):
// - WebView 承载琉璃镜(本地构建产物或开发服务器);
// - 本地能力桥:器谱授契校验后的最小命令(通知/文件对话框/语音输入暂存);
// - 乾坤通道:HMAC 短期凭证构造(与 sandbox/src/auth.rs 同算法),执行仍经灵台司命裁决;
// - 本地硬急停:一键断开全部通道(本地优先级最高,不经云端);
// - tantivy 本地检索:依赖已就位,目录预留(M9 不做实现)。
//
// 禁止:内置任何平台密钥;未授契能力直通;本地保有人物副本。
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use base64::Engine;
use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
/// 镜匣运行状态:硬急停开关(进程内存态;触发即断全部通道直到用户显式恢复)。
struct ShellState {
halted: std::sync::atomic::AtomicBool,
}
impl ShellState {
fn new() -> Self {
Self {
halted: std::sync::atomic::AtomicBool::new(false),
}
}
}
fn b64url(data: &[u8]) -> String {
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
URL_SAFE_NO_PAD.encode(data)
}
/// 乾坤 HMAC 短期凭证(与 sandbox 验签算法对齐):
/// payload = tool|trace_id|risk_level|body_hash|expires_at,sig = HMAC-SHA256(secret, payload_b64url)。
/// 密钥来自本地配置(环境变量,绝不内置于二进制)。
fn build_qiankun_grant(
secret: &str,
tool: &str,
trace_id: &str,
risk_level: &str,
body_hash: &str,
ttl_seconds: i64,
) -> Result<serde_json::Value, String> {
let expires_at = chrono_now_epoch() + ttl_seconds;
let payload = serde_json::json!({
"tool": tool,
"trace_id": trace_id,
"risk_level": risk_level,
"body_hash": body_hash,
"expires_at": expires_at,
});
let payload_b64 = b64url(payload.to_string().as_bytes());
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
.map_err(|e| format!("HMAC 初始化失败: {e}"))?;
mac.update(payload_b64.as_bytes());
let sig = b64url(&mac.finalize().into_bytes());
Ok(serde_json::json!({
"payload_b64url": payload_b64,
"sig": sig,
"expires_at": expires_at,
}))
}
fn chrono_now_epoch() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
#[tauri::command]
/// 能力桥:通知(须器谱已授 notifications——校验经灵台 /devices 断言,此处只发)。
fn send_notification(state: tauri::State<ShellState>, title: String, body: String) -> Result<(), String> {
ensure_not_halted(&state)?;
// Tauri v2 通知需插件;最小壳以日志承载(插件接入随正式打包)
println!("[镜匣·通知] {title}: {body}");
Ok(())
}
#[tauri::command]
/// 乾坤通道:构造 HMAC 短期凭证(密钥读自环境 SANDBOX_AUTH_SECRET——零内置密钥)。
fn qiankun_grant(
state: tauri::State<ShellState>,
tool: String,
trace_id: String,
risk_level: String,
body_hash: String,
) -> Result<serde_json::Value, String> {
ensure_not_halted(&state)?;
let secret = std::env::var("SANDBOX_AUTH_SECRET")
.map_err(|_| "乾坤密钥未配置(SANDBOX_AUTH_SECRET),通道关闭".to_string())?;
if secret.is_empty() {
return Err("乾坤密钥为空,通道关闭".into());
}
build_qiankun_grant(&secret, &tool, &trace_id, &risk_level, &body_hash, 300)
}
#[tauri::command]
/// 硬急停:断开全部通道(本地最高优先级,不经云端;恢复需显式 resume)。
fn hard_stop(state: tauri::State<ShellState>) -> Result<String, String> {
state
.halted
.store(true, std::sync::atomic::Ordering::SeqCst);
Ok("halted".into())
}
#[tauri::command]
/// 硬急停恢复(用户显式操作)。
fn resume(state: tauri::State<ShellState>) -> Result<String, String> {
state
.halted
.store(false, std::sync::atomic::Ordering::SeqCst);
Ok("resumed".into())
}
#[tauri::command]
fn is_halted(state: tauri::State<ShellState>) -> bool {
state.halted.load(std::sync::atomic::Ordering::SeqCst)
}
fn ensure_not_halted(state: &ShellState) -> Result<(), String> {
if state.halted.load(std::sync::atomic::Ordering::SeqCst) {
Err("硬急停已触发:全部通道关闭".into())
} else {
Ok(())
}
}
fn main() {
tauri::Builder::default()
.manage(ShellState::new())
.invoke_handler(tauri::generate_handler![
send_notification,
qiankun_grant,
hard_stop,
resume,
is_halted,
])
.run(tauri::generate_context!())
.expect("镜匣启动失败");
}
+32
View File
@@ -0,0 +1,32 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "镜匣",
"version": "0.1.0",
"identifier": "hunjing.jingxia",
"build": {
"frontendDist": "../frontend/out",
"devUrl": "http://localhost:3000",
"beforeDevCommand": "",
"beforeBuildCommand": ""
},
"app": {
"windows": [
{
"title": "魂镜 · 镜匣",
"width": 1280,
"height": 800,
"minWidth": 960,
"minHeight": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": ["icons/icon.png"]
},
"plugins": {}
}