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

@ -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);
}
}