feat: load authoritative empty persona trust registry

This commit is contained in:
冰朔 2026-08-12 05:37:40 +08:00
commit 9281587a0f
11 changed files with 343 additions and 15 deletions

View file

@ -119,9 +119,10 @@ REVISE | REFUSE`。
- 人格主控授权回执的结构与精确绑定校验源码为 `100`:不再接受桌面调用方直接传入布尔值,并核对
人格、冰朔责任主体、仓库提交、模型实例、请求、语言锚点、摘要格式和有效期。但这层前端结构校验
不能证明回执确由 `GUANGHU_OS` 签发。独立的 v2 回执、REPO-012 精确提交签名者注册表、规范签名字节、
人格/责任主体/范围约束、吊销和 Ed25519 验签源码契约现为 `100`;但 REPO-012 当前 main 未发布该注册表,
因而真实可信签名者登记、Tauri 原生验签加载器与运行集成仍为 `0`。在同一条可回读证据链形成以前,
普通本地 JSON 不得升级人格主控,桌面继续系统直控。
人格/责任主体/范围约束、吊销和 Ed25519 验签源码契约现为 `100`。REPO-012 main
`21aec6f5042e32e34c892a7a212d8dec5f758437` 已发布锚定的空注册表Tauri 原生只读加载器源码也为
`100`:它只接受同一精确提交的空注册表,遇到任何签名者都失败关闭且保持授权禁用。真实可信签名者、
密钥托管、原生签名回执验签与人格主控运行集成仍为 `0`;普通本地 JSON 不得升级人格主控,桌面继续系统直控。
- 完整 HoloLake Runtime 与单 AGE 纵向闭环仍为 `0`:真实人格仓库 manifest 绑定和桌面运行验收尚未完成,
因此不能用本轮源码测试冒充可用产品。
- Mirror runner、制品、部署和运行健康`0`

View file

@ -25,11 +25,13 @@ The source coordinator no longer accepts an unverified persona-authorization boo
`hasPersonaPrimaryControlAuthorization` validates the shape, exact bindings, digest format, and validity window
of a receipt that claims `GUANGHU_OS` verification. That renderer-side validation is not cryptographic provenance
and cannot turn a local JSON object into authority. A native loader may be connected only after it verifies a
signed receipt against a registered Guanghu OS trust source and returns the already-verified projection. No such
trust source or loader is currently registered, so planning remains in system-direct mode. The independent server
signed receipt against a registered Guanghu OS trust source and returns the already-verified projection. REPO-012
now publishes the exact registry source with zero signers, and the Tauri command loads only that exact commit-bound
empty state. It keeps authorization disabled and rejects any signer until native cryptographic verification exists,
so planning remains in system-direct mode. The independent server
contract in `product-source/guanghu-knowledge-base/server/persona-control-authorization.ts` defines the v2 signed
receipt, exact REPO-012 signer-registry source, canonical signing bytes, signer scope checks, revocation handling,
and Ed25519 verification. Its tested code does not populate the registry or connect the desktop native boundary.
and Ed25519 verification. Its tested code does not appoint a signer or enable the desktop persona-primary path.
`compilePersonaLanguageGoalBinding` builds the exact native wake envelope only after a single clean persona
repository, its B0 and organ contracts, a registered local device identity, and the manifest-pinned model all

View file

@ -189,7 +189,10 @@ current desktop source. Missing, stale, mismatched or merely local evidence ther
cryptographic source contract: v2 signed receipt fields, deterministic signing bytes, an exact-commit REPO-012
signer-registry source, persona/human/scope-limited Ed25519 signers, revocation, time and request binding, and
signature verification. The current REPO-012 main does not publish that registry path, so no signer is trusted and
the Tauri desktop has no native verifier or loader. Tested verifier source is not runtime authority.
the Tauri desktop now exposes a native read-only loader for the exact REPO-012 commit-bound registry. It accepts
the current empty registry only and keeps authorization disabled; any signer fails closed until the native
cryptographic receipt verifier and an authoritative signer/key-custody process exist. Tested loader and verifier
source are not runtime authority, deployment, or desktop acceptance.
Tolaria is a personal knowledge and life management desktop app. It reads a vault of markdown files with YAML frontmatter and presents them in a four-panel UI inspired by Bear Notes.

View file

@ -37,6 +37,7 @@ mod opencode_config;
mod opencode_discovery;
mod opencode_events;
mod persona_code_channel;
mod persona_control_authorization;
mod persona_remote_git;
pub mod pi_cli;
mod pi_config;
@ -524,6 +525,7 @@ macro_rules! app_invoke_handler {
commands::git_add_remote,
guanghu_enterprise::guanghu_enterprise_status,
guanghu_living_system::guanghu_living_system_plan,
persona_control_authorization::load_persona_control_authorization_registry,
persona_code_channel::prepare_persona_code_channel_wake,
persona_code_channel::inspect_persona_code_channel_manifest,
persona_code_channel::discover_persona_code_channel_repositories,

View file

@ -0,0 +1,304 @@
use reqwest::Client;
use serde::Serialize;
use serde_json::Value;
use std::time::Duration;
const ANCHOR_URL: &str = "https://guanghulab.com/api/ai/v1/anchor";
const CODE_URL: &str = "https://guanghulab.com/code/bingshuo/guanghu-ice-heart";
const REGISTRY_ID: &str = "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001";
const REGISTRY_PATH: &str = "routing/persona-control-authorization-signers.json";
const REGISTRY_SCHEMA: &str = "gh-aios.persona-control-authorization-signers/v1";
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NativePersonaAuthorizationRegistryReceipt {
status: &'static str,
reason: Option<&'static str>,
source_commit: Option<String>,
signer_count: usize,
authorization_enabled: bool,
}
fn unavailable(reason: &'static str) -> NativePersonaAuthorizationRegistryReceipt {
NativePersonaAuthorizationRegistryReceipt {
status: "UNAVAILABLE",
reason: Some(reason),
source_commit: None,
signer_count: 0,
authorization_enabled: false,
}
}
fn is_commit(value: &str) -> bool {
matches!(value.len(), 40 | 64)
&& value
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}
fn parse_anchor(
input: &Value,
expected_anchor_url: &str,
expected_code_url: &str,
) -> Result<String, &'static str> {
let map = input
.pointer("/maps/persona_control_authorization_signers")
.and_then(Value::as_object)
.ok_or("ANCHOR_INVALID")?;
let navigation = input
.get("navigation_source")
.and_then(Value::as_object)
.ok_or("ANCHOR_INVALID")?;
let source_commit = navigation
.get("source_commit")
.and_then(Value::as_str)
.ok_or("ANCHOR_INVALID")?;
if input.get("schema").and_then(Value::as_str) != Some("guanghu.public-navigation-anchor/v1")
|| input.get("anchor_id").and_then(Value::as_str) != Some("GLW-PUBLIC-NAV-ANCHOR-001")
|| input.get("state").and_then(Value::as_str) != Some("CURRENT_CANONICAL")
|| input.get("repository_id").and_then(Value::as_str) != Some("REPO-012")
|| input.get("branch").and_then(Value::as_str) != Some("main")
|| input.get("public_entry").and_then(Value::as_str) != Some(expected_anchor_url)
|| input.get("code_entry").and_then(Value::as_str) != Some(expected_code_url)
|| map.len() != 4
|| map.get("path").and_then(Value::as_str) != Some(REGISTRY_PATH)
|| map.get("id").and_then(Value::as_str) != Some(REGISTRY_ID)
|| map.get("schema").and_then(Value::as_str) != Some(REGISTRY_SCHEMA)
|| map.get("state").and_then(Value::as_str) != Some("CURRENT_EMPTY_NO_TRUSTED_SIGNER")
|| navigation.get("anchor_id").and_then(Value::as_str) != Some("GLW-PUBLIC-NAV-ANCHOR-001")
|| navigation.get("source_mode").and_then(Value::as_str)
!= Some("REPO-012_MAIN_GIT_SNAPSHOT")
|| navigation.get("source_degraded").and_then(Value::as_bool) != Some(false)
|| !is_commit(source_commit)
{
return Err("ANCHOR_INVALID");
}
Ok(source_commit.to_string())
}
fn validate_empty_registry(input: &Value) -> Result<(), &'static str> {
let registry = input.as_object().ok_or("REGISTRY_INVALID")?;
let signers = registry
.get("signers")
.and_then(Value::as_array)
.ok_or("REGISTRY_INVALID")?;
if registry.len() != 4
|| registry.get("schema").and_then(Value::as_str) != Some(REGISTRY_SCHEMA)
|| registry.get("registryId").and_then(Value::as_str) != Some(REGISTRY_ID)
|| registry.get("state").and_then(Value::as_str) != Some("CURRENT")
{
return Err("REGISTRY_INVALID");
}
if !signers.is_empty() {
return Err("TRUSTED_SIGNER_REQUIRES_NATIVE_VERIFIER");
}
Ok(())
}
fn registry_url(code_url: &str, source_commit: &str) -> String {
format!("{code_url}/raw/commit/{source_commit}/{REGISTRY_PATH}")
}
async fn fetch_json(client: &Client, url: &str) -> Result<Value, ()> {
let response = client
.get(url)
.header("accept", "application/json")
.send()
.await
.map_err(|_| ())?;
if !response.status().is_success() {
return Err(());
}
response.json::<Value>().await.map_err(|_| ())
}
async fn load_with_urls(
client: &Client,
anchor_url: &str,
code_url: &str,
) -> NativePersonaAuthorizationRegistryReceipt {
let anchor = match fetch_json(client, anchor_url).await {
Ok(value) => value,
Err(()) => return unavailable("SOURCE_UNAVAILABLE"),
};
let source_commit = match parse_anchor(&anchor, anchor_url, code_url) {
Ok(value) => value,
Err(reason) => return unavailable(reason),
};
let registry = match fetch_json(client, &registry_url(code_url, &source_commit)).await {
Ok(value) => value,
Err(()) => return unavailable("SOURCE_UNAVAILABLE"),
};
if let Err(reason) = validate_empty_registry(&registry) {
return unavailable(reason);
}
NativePersonaAuthorizationRegistryReceipt {
status: "CURRENT_EMPTY",
reason: None,
source_commit: Some(source_commit),
signer_count: 0,
authorization_enabled: false,
}
}
#[tauri::command]
pub async fn load_persona_control_authorization_registry(
) -> NativePersonaAuthorizationRegistryReceipt {
let client = match Client::builder()
.connect_timeout(Duration::from_secs(8))
.timeout(Duration::from_secs(15))
.redirect(reqwest::redirect::Policy::none())
.build()
{
Ok(client) => client,
Err(_) => return unavailable("CLIENT_UNAVAILABLE"),
};
load_with_urls(&client, ANCHOR_URL, CODE_URL).await
}
#[cfg(test)]
mod tests {
use super::{
load_with_urls, parse_anchor, registry_url, validate_empty_registry, REGISTRY_PATH,
};
use reqwest::Client;
use serde_json::{json, Value};
use std::io::{Read, Write};
use std::net::TcpListener;
use std::thread;
const COMMIT: &str = "21aec6f5042e32e34c892a7a212d8dec5f758437";
fn anchor(anchor_url: &str, code_url: &str) -> Value {
json!({
"schema": "guanghu.public-navigation-anchor/v1",
"anchor_id": "GLW-PUBLIC-NAV-ANCHOR-001",
"state": "CURRENT_CANONICAL",
"repository_id": "REPO-012",
"branch": "main",
"public_entry": anchor_url,
"code_entry": code_url,
"maps": {
"persona_control_authorization_signers": {
"path": REGISTRY_PATH,
"id": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001",
"schema": "gh-aios.persona-control-authorization-signers/v1",
"state": "CURRENT_EMPTY_NO_TRUSTED_SIGNER"
}
},
"navigation_source": {
"anchor_id": "GLW-PUBLIC-NAV-ANCHOR-001",
"source_commit": COMMIT,
"source_mode": "REPO-012_MAIN_GIT_SNAPSHOT",
"source_degraded": false
}
})
}
fn empty_registry() -> Value {
json!({
"schema": "gh-aios.persona-control-authorization-signers/v1",
"registryId": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001",
"state": "CURRENT",
"signers": []
})
}
fn serve_snapshot() -> (String, String) {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind test snapshot server");
let address = listener.local_addr().expect("snapshot server address");
let anchor_url = format!("http://{address}/anchor");
let code_url = format!("http://{address}/code");
let anchor_body = anchor(&anchor_url, &code_url).to_string();
let registry_body = empty_registry().to_string();
thread::spawn(move || {
for _ in 0..2 {
let (mut stream, _) = listener.accept().expect("accept snapshot request");
let mut request = [0_u8; 4096];
let read = stream.read(&mut request).expect("read snapshot request");
let request = String::from_utf8_lossy(&request[..read]);
let body = if request.starts_with("GET /anchor ") {
&anchor_body
} else {
assert!(request.contains(&format!("/raw/commit/{COMMIT}/{REGISTRY_PATH}")));
&registry_body
};
write!(
stream,
"HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}",
body.len()
)
.expect("write snapshot response");
}
});
(anchor_url, code_url)
}
#[test]
fn exact_anchor_and_registry_are_bound_to_one_commit() {
let anchor_url = "https://example.test/anchor";
let code_url = "https://example.test/code";
assert_eq!(
parse_anchor(&anchor(anchor_url, code_url), anchor_url, code_url),
Ok(COMMIT.into())
);
assert_eq!(
registry_url(code_url, COMMIT),
format!("{code_url}/raw/commit/{COMMIT}/{REGISTRY_PATH}")
);
assert_eq!(validate_empty_registry(&empty_registry()), Ok(()));
}
#[test]
fn degraded_or_changed_sources_and_nonempty_registries_fail_closed() {
let anchor_url = "https://example.test/anchor";
let code_url = "https://example.test/code";
let mut degraded = anchor(anchor_url, code_url);
degraded["navigation_source"]["source_degraded"] = json!(true);
assert_eq!(
parse_anchor(&degraded, anchor_url, code_url),
Err("ANCHOR_INVALID")
);
let mut wrong_path = anchor(anchor_url, code_url);
wrong_path["maps"]["persona_control_authorization_signers"]["path"] =
json!("routing/other.json");
assert_eq!(
parse_anchor(&wrong_path, anchor_url, code_url),
Err("ANCHOR_INVALID")
);
let mut nonempty = empty_registry();
nonempty["signers"] = json!([{"signerId": "UNVERIFIED"}]);
assert_eq!(
validate_empty_registry(&nonempty),
Err("TRUSTED_SIGNER_REQUIRES_NATIVE_VERIFIER")
);
assert_eq!(validate_empty_registry(&json!([])), Err("REGISTRY_INVALID"));
}
#[tokio::test]
async fn native_transport_loads_the_exact_empty_registry_without_enabling_authorization() {
let (anchor_url, code_url) = serve_snapshot();
let client = Client::builder().build().expect("test client");
let receipt = load_with_urls(&client, &anchor_url, &code_url).await;
let projection = serde_json::to_value(receipt).expect("serialize receipt");
assert_eq!(projection["status"], "CURRENT_EMPTY");
assert_eq!(projection["sourceCommit"], COMMIT);
assert_eq!(projection["signerCount"], 0);
assert_eq!(projection["authorizationEnabled"], false);
assert!(projection["reason"].is_null());
}
#[tokio::test]
async fn source_and_anchor_failures_return_bounded_unavailable_receipts() {
let listener = TcpListener::bind("127.0.0.1:0").expect("reserve test port");
let unavailable = format!("http://{}/anchor", listener.local_addr().unwrap());
drop(listener);
let client = Client::builder().build().expect("test client");
let source_failure =
serde_json::to_value(load_with_urls(&client, &unavailable, "http://unused").await)
.unwrap();
assert_eq!(source_failure["status"], "UNAVAILABLE");
assert_eq!(source_failure["reason"], "SOURCE_UNAVAILABLE");
assert_eq!(source_failure["authorizationEnabled"], false);
}
}

View file

@ -56,7 +56,7 @@ describe('PersonaLanguageShellPanel', () => {
repositoryPaths: ['/persona'],
providers: [provider],
developmentId: 'DEV-20260811-010',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.12',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.13',
}))
expect(await screen.findByText('我还不能执行:需要当前证据。')).toBeInTheDocument()
expect(screen.getByText('核验当前事实后规划下一步')).toBeInTheDocument()

View file

@ -13,7 +13,7 @@ import {
type LanguageShellViewState,
} from './HotPluggableLanguageShell'
const CURRENT_ARCHITECTURE_ANCHOR = 'HLP-CURRENT-ARCH-001@2026-08-12.12'
const CURRENT_ARCHITECTURE_ANCHOR = 'HLP-CURRENT-ARCH-001@2026-08-12.13'
const DEVELOPMENT_ID = 'DEV-20260811-010'
type Planner = typeof planPersonaLanguageShellGoal
@ -103,8 +103,9 @@ export function PersonaLanguageShellPanel({
personaId,
repositoryPaths,
providers,
// No native authorization receipt loader is wired yet. The controller
// therefore remains system-direct even after deliberation succeeds.
// The native registry loader currently proves only an exact empty trust
// source. No native signed-receipt verifier is wired to this controller,
// so it remains system-direct even after deliberation succeeds.
requestId: requestId(),
modelInstanceId: `hololake-desktop:${personaId}`,
developmentId: DEVELOPMENT_ID,

View file

@ -11,7 +11,7 @@ const expected = {
repositoryHead: 'a'.repeat(40),
modelInstanceId: 'MODEL-INSTANCE-001',
requestId: 'REQ-001',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.12',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.13',
observedAt: Date.parse('2026-08-12T04:35:00+08:00'),
}