feat: verify signed persona control receipts natively

This commit is contained in:
冰朔 2026-08-12 05:50:53 +08:00
commit 75b6262c91
11 changed files with 562 additions and 98 deletions

View file

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

View file

@ -27,8 +27,8 @@ of a receipt that claims `GUANGHU_OS` verification. That renderer-side validatio
and cannot turn a local JSON object into authority. A native loader may be connected only after it verifies a 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. REPO-012 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 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, empty state. The native verifier now validates the complete signed v2 contract, but no verified projection is wired
so planning remains in system-direct mode. The independent server into the language controller and the authoritative registry has zero signers, 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 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, receipt, exact REPO-012 signer-registry source, canonical signing bytes, signer scope checks, revocation handling,
and Ed25519 verification. Its tested code does not appoint a signer or enable the desktop persona-primary path. and Ed25519 verification. Its tested code does not appoint a signer or enable the desktop persona-primary path.

View file

@ -190,9 +190,10 @@ cryptographic source contract: v2 signed receipt fields, deterministic signing b
signer-registry source, persona/human/scope-limited Ed25519 signers, revocation, time and request binding, and 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 signature verification. The current REPO-012 main does not publish that registry path, so no signer is trusted and
the Tauri desktop now exposes a native read-only loader for the exact REPO-012 commit-bound registry. It accepts 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 the current empty registry and keeps authorization disabled. The native verifier now implements strict v2 receipt
cryptographic receipt verifier and an authoritative signer/key-custody process exist. Tested loader and verifier parsing, canonical signing bytes, Ed25519 SPKI verification, signer scope/revocation checks, exact request bindings,
source are not runtime authority, deployment, or desktop acceptance. and validity windows. It is not wired into the language controller, and the authoritative registry still has zero
signers, so tested loader/verifier source is 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. 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

@ -526,6 +526,7 @@ macro_rules! app_invoke_handler {
guanghu_enterprise::guanghu_enterprise_status, guanghu_enterprise::guanghu_enterprise_status,
guanghu_living_system::guanghu_living_system_plan, guanghu_living_system::guanghu_living_system_plan,
persona_control_authorization::load_persona_control_authorization_registry, persona_control_authorization::load_persona_control_authorization_registry,
persona_control_authorization::verify_persona_control_authorization,
persona_code_channel::prepare_persona_code_channel_wake, persona_code_channel::prepare_persona_code_channel_wake,
persona_code_channel::inspect_persona_code_channel_manifest, persona_code_channel::inspect_persona_code_channel_manifest,
persona_code_channel::discover_persona_code_channel_repositories, persona_code_channel::discover_persona_code_channel_repositories,

View file

@ -1,13 +1,21 @@
use base64::{engine::general_purpose, Engine as _};
use reqwest::Client; use reqwest::Client;
use serde::Serialize; use ring::signature::{UnparsedPublicKey, ED25519};
use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use std::time::Duration; use std::time::Duration;
const ANCHOR_URL: &str = "https://guanghulab.com/api/ai/v1/anchor"; const ANCHOR_URL: &str = "https://guanghulab.com/api/ai/v1/anchor";
const CODE_URL: &str = "https://guanghulab.com/code/bingshuo/guanghu-ice-heart"; const CODE_URL: &str = "https://guanghulab.com/code/bingshuo/guanghu-ice-heart";
const RECEIPT_SCHEMA: &str = "hololake.persona-control-authorization/v2";
const REGISTRY_ID: &str = "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001"; const REGISTRY_ID: &str = "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001";
const REGISTRY_PATH: &str = "routing/persona-control-authorization-signers.json"; const REGISTRY_PATH: &str = "routing/persona-control-authorization-signers.json";
const REGISTRY_SCHEMA: &str = "gh-aios.persona-control-authorization-signers/v1"; const REGISTRY_SCHEMA: &str = "gh-aios.persona-control-authorization-signers/v1";
const REQUIRED_SCOPE: &str = "PERSONA_PRIMARY_LANGUAGE_PLANNING";
const SIGNING_CONTEXT: &str = "hololake.persona-control-authorization/signing/v1";
const ED25519_SPKI_PREFIX: [u8; 12] = [
0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
];
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -19,6 +27,76 @@ pub struct NativePersonaAuthorizationRegistryReceipt {
authorization_enabled: bool, authorization_enabled: bool,
} }
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NativePersonaAuthorizationVerificationReceipt {
status: &'static str,
reason: Option<&'static str>,
source_commit: Option<String>,
signer_id: Option<String>,
authorization_enabled: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct NativePersonaAuthorizationExpectation {
persona_id: String,
human_responsibility_subject: String,
repository_head: String,
model_instance_id: String,
request_id: String,
source_language_anchor: String,
observed_at_milliseconds: i64,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct PersonaAuthorizationSigner {
algorithm: String,
human_responsibility_subjects: Vec<String>,
persona_ids: Vec<String>,
public_key_pem: String,
scopes: Vec<String>,
signer_id: String,
status: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct PersonaAuthorizationRegistry {
schema: String,
registry_id: String,
state: String,
signers: Vec<PersonaAuthorizationSigner>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct PersonaAuthorizationReceipt {
schema: String,
outcome: String,
authorization_id: String,
verifier: String,
scope: String,
persona_id: String,
human_responsibility_subject: String,
repository_head: String,
model_instance_id: String,
request_id: String,
source_language_anchor: String,
issued_at: String,
valid_until: String,
evidence_digest: String,
signer_id: String,
signature_algorithm: String,
signature: String,
}
struct LoadedRegistry {
source_commit: String,
registry: PersonaAuthorizationRegistry,
}
fn unavailable(reason: &'static str) -> NativePersonaAuthorizationRegistryReceipt { fn unavailable(reason: &'static str) -> NativePersonaAuthorizationRegistryReceipt {
NativePersonaAuthorizationRegistryReceipt { NativePersonaAuthorizationRegistryReceipt {
status: "UNAVAILABLE", status: "UNAVAILABLE",
@ -29,13 +107,93 @@ fn unavailable(reason: &'static str) -> NativePersonaAuthorizationRegistryReceip
} }
} }
fn is_commit(value: &str) -> bool { fn denied(
matches!(value.len(), 40 | 64) reason: &'static str,
source_commit: Option<String>,
) -> NativePersonaAuthorizationVerificationReceipt {
NativePersonaAuthorizationVerificationReceipt {
status: "DENIED",
reason: Some(reason),
source_commit,
signer_id: None,
authorization_enabled: false,
}
}
fn is_lower_hex(value: &str, length: usize) -> bool {
value.len() == length
&& value && value
.bytes() .bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
} }
fn is_commit(value: &str) -> bool {
is_lower_hex(value, 40) || is_lower_hex(value, 64)
}
fn is_identifier(value: &str) -> bool {
let mut bytes = value.bytes();
let Some(first) = bytes.next() else {
return false;
};
(2..=160).contains(&value.len())
&& (first.is_ascii_uppercase() || first.is_ascii_digit())
&& bytes.all(|byte| {
byte.is_ascii_uppercase()
|| byte.is_ascii_digit()
|| matches!(byte, b'.' | b'_' | b':' | b'@' | b'-')
})
}
fn exact_identifier_list(values: &[String]) -> bool {
!values.is_empty()
&& values.iter().all(|value| is_identifier(value))
&& values
.iter()
.enumerate()
.all(|(index, value)| !values[..index].contains(value))
}
fn ed25519_public_key(public_key_pem: &str) -> Result<Vec<u8>, &'static str> {
if public_key_pem.contains("PRIVATE KEY")
|| !public_key_pem.starts_with("-----BEGIN PUBLIC KEY-----\n")
|| !public_key_pem.ends_with("-----END PUBLIC KEY-----\n")
|| public_key_pem.len() > 4096
{
return Err("REGISTRY_INVALID");
}
let encoded = public_key_pem
.trim_start_matches("-----BEGIN PUBLIC KEY-----\n")
.trim_end_matches("-----END PUBLIC KEY-----\n")
.chars()
.filter(|character| !character.is_ascii_whitespace())
.collect::<String>();
let der = general_purpose::STANDARD
.decode(encoded)
.map_err(|_| "REGISTRY_INVALID")?;
if der.len() != ED25519_SPKI_PREFIX.len() + 32
|| der[..ED25519_SPKI_PREFIX.len()] != ED25519_SPKI_PREFIX
{
return Err("REGISTRY_INVALID");
}
Ok(der[ED25519_SPKI_PREFIX.len()..].to_vec())
}
fn validate_signer(signer: &PersonaAuthorizationSigner) -> Result<(), &'static str> {
if signer.algorithm != "Ed25519"
|| !is_identifier(&signer.signer_id)
|| !matches!(signer.status.as_str(), "ACTIVE" | "REVOKED")
|| !exact_identifier_list(&signer.human_responsibility_subjects)
|| !exact_identifier_list(&signer.persona_ids)
|| !exact_identifier_list(&signer.scopes)
|| !signer.scopes.iter().any(|scope| scope == REQUIRED_SCOPE)
{
return Err("REGISTRY_INVALID");
}
ed25519_public_key(&signer.public_key_pem)?;
Ok(())
}
fn parse_anchor( fn parse_anchor(
input: &Value, input: &Value,
expected_anchor_url: &str, expected_anchor_url: &str,
@ -76,23 +234,22 @@ fn parse_anchor(
Ok(source_commit.to_string()) Ok(source_commit.to_string())
} }
fn validate_empty_registry(input: &Value) -> Result<(), &'static str> { fn parse_registry(input: Value) -> Result<PersonaAuthorizationRegistry, &'static str> {
let registry = input.as_object().ok_or("REGISTRY_INVALID")?; let registry: PersonaAuthorizationRegistry =
let signers = registry serde_json::from_value(input).map_err(|_| "REGISTRY_INVALID")?;
.get("signers") if registry.schema != REGISTRY_SCHEMA
.and_then(Value::as_array) || registry.registry_id != REGISTRY_ID
.ok_or("REGISTRY_INVALID")?; || registry.state != "CURRENT"
if registry.len() != 4 || registry.signers.iter().enumerate().any(|(index, signer)| {
|| registry.get("schema").and_then(Value::as_str) != Some(REGISTRY_SCHEMA) validate_signer(signer).is_err()
|| registry.get("registryId").and_then(Value::as_str) != Some(REGISTRY_ID) || registry.signers[..index]
|| registry.get("state").and_then(Value::as_str) != Some("CURRENT") .iter()
.any(|prior| prior.signer_id == signer.signer_id)
})
{ {
return Err("REGISTRY_INVALID"); return Err("REGISTRY_INVALID");
} }
if !signers.is_empty() { Ok(registry)
return Err("TRUSTED_SIGNER_REQUIRES_NATIVE_VERIFIER");
}
Ok(())
} }
fn registry_url(code_url: &str, source_commit: &str) -> String { fn registry_url(code_url: &str, source_commit: &str) -> String {
@ -112,56 +269,207 @@ async fn fetch_json(client: &Client, url: &str) -> Result<Value, ()> {
response.json::<Value>().await.map_err(|_| ()) response.json::<Value>().await.map_err(|_| ())
} }
async fn load_snapshot(
client: &Client,
anchor_url: &str,
code_url: &str,
) -> Result<LoadedRegistry, &'static str> {
let anchor = fetch_json(client, anchor_url)
.await
.map_err(|_| "SOURCE_UNAVAILABLE")?;
let source_commit = parse_anchor(&anchor, anchor_url, code_url)?;
let registry = fetch_json(client, &registry_url(code_url, &source_commit))
.await
.map_err(|_| "SOURCE_UNAVAILABLE")?;
Ok(LoadedRegistry {
source_commit,
registry: parse_registry(registry)?,
})
}
async fn load_with_urls( async fn load_with_urls(
client: &Client, client: &Client,
anchor_url: &str, anchor_url: &str,
code_url: &str, code_url: &str,
) -> NativePersonaAuthorizationRegistryReceipt { ) -> NativePersonaAuthorizationRegistryReceipt {
let anchor = match fetch_json(client, anchor_url).await { match load_snapshot(client, anchor_url, code_url).await {
Ok(value) => value, Ok(snapshot) => NativePersonaAuthorizationRegistryReceipt {
Err(()) => return unavailable("SOURCE_UNAVAILABLE"), status: if snapshot.registry.signers.is_empty() {
}; "CURRENT_EMPTY"
let source_commit = match parse_anchor(&anchor, anchor_url, code_url) { } else {
Ok(value) => value, "CURRENT"
Err(reason) => return unavailable(reason), },
}; reason: None,
let registry = match fetch_json(client, &registry_url(code_url, &source_commit)).await { source_commit: Some(snapshot.source_commit),
Ok(value) => value, signer_count: snapshot.registry.signers.len(),
Err(()) => return unavailable("SOURCE_UNAVAILABLE"), authorization_enabled: false,
}; },
if let Err(reason) = validate_empty_registry(&registry) { Err(reason) => unavailable(reason),
return unavailable(reason);
} }
NativePersonaAuthorizationRegistryReceipt { }
status: "CURRENT_EMPTY",
fn parse_receipt(input: Value) -> Result<PersonaAuthorizationReceipt, &'static str> {
let receipt: PersonaAuthorizationReceipt =
serde_json::from_value(input).map_err(|_| "RECEIPT_INVALID")?;
if receipt.schema != RECEIPT_SCHEMA
|| receipt.outcome != "VERIFIED"
|| receipt.verifier != "GUANGHU_OS"
|| receipt.scope != REQUIRED_SCOPE
|| receipt.signature_algorithm != "Ed25519"
|| !is_identifier(&receipt.authorization_id)
|| !is_identifier(&receipt.persona_id)
|| !is_identifier(&receipt.human_responsibility_subject)
|| !is_commit(&receipt.repository_head)
|| !is_identifier(&receipt.model_instance_id)
|| !is_identifier(&receipt.request_id)
|| !is_identifier(&receipt.signer_id)
|| !is_lower_hex(&receipt.evidence_digest, 64)
|| !(80..=128).contains(&receipt.signature.len())
{
return Err("RECEIPT_INVALID");
}
Ok(receipt)
}
fn signing_bytes(receipt: &PersonaAuthorizationReceipt) -> Result<Vec<u8>, &'static str> {
let fields = [
SIGNING_CONTEXT,
&receipt.schema,
&receipt.outcome,
&receipt.authorization_id,
&receipt.verifier,
&receipt.scope,
&receipt.persona_id,
&receipt.human_responsibility_subject,
&receipt.repository_head,
&receipt.model_instance_id,
&receipt.request_id,
&receipt.source_language_anchor,
&receipt.issued_at,
&receipt.valid_until,
&receipt.evidence_digest,
&receipt.signer_id,
&receipt.signature_algorithm,
];
serde_json::to_string(&fields)
.map(|json| format!("{json}\n").into_bytes())
.map_err(|_| "RECEIPT_INVALID")
}
fn verify_receipt(
input: Value,
expectation: &NativePersonaAuthorizationExpectation,
snapshot: LoadedRegistry,
) -> NativePersonaAuthorizationVerificationReceipt {
let source_commit = Some(snapshot.source_commit.clone());
let receipt = match parse_receipt(input) {
Ok(receipt) => receipt,
Err(reason) => return denied(reason, source_commit),
};
let issued_at = match chrono::DateTime::parse_from_rfc3339(&receipt.issued_at) {
Ok(value) => value.timestamp_millis(),
Err(_) => return denied("RECEIPT_TIME_INVALID", source_commit),
};
let valid_until = match chrono::DateTime::parse_from_rfc3339(&receipt.valid_until) {
Ok(value) => value.timestamp_millis(),
Err(_) => return denied("RECEIPT_TIME_INVALID", source_commit),
};
if receipt.persona_id != expectation.persona_id
|| receipt.human_responsibility_subject != expectation.human_responsibility_subject
|| receipt.repository_head != expectation.repository_head
|| receipt.model_instance_id != expectation.model_instance_id
|| receipt.request_id != expectation.request_id
|| receipt.source_language_anchor != expectation.source_language_anchor
|| issued_at >= valid_until
|| issued_at > expectation.observed_at_milliseconds
|| expectation.observed_at_milliseconds > valid_until
{
return denied("RECEIPT_BINDING_INVALID", source_commit);
}
let Some(signer) = snapshot.registry.signers.iter().find(|signer| {
signer.status == "ACTIVE"
&& signer.signer_id == receipt.signer_id
&& signer.persona_ids.contains(&receipt.persona_id)
&& signer
.human_responsibility_subjects
.contains(&receipt.human_responsibility_subject)
&& signer.scopes.contains(&receipt.scope)
}) else {
return denied("NO_TRUSTED_SIGNER", source_commit);
};
let public_key = match ed25519_public_key(&signer.public_key_pem) {
Ok(value) => value,
Err(_) => return denied("REGISTRY_INVALID", source_commit),
};
let signature = match general_purpose::URL_SAFE_NO_PAD.decode(&receipt.signature) {
Ok(value) if value.len() == 64 => value,
_ => return denied("SIGNATURE_INVALID", source_commit),
};
let message = match signing_bytes(&receipt) {
Ok(value) => value,
Err(reason) => return denied(reason, source_commit),
};
if UnparsedPublicKey::new(&ED25519, public_key)
.verify(&message, &signature)
.is_err()
{
return denied("SIGNATURE_INVALID", source_commit);
}
NativePersonaAuthorizationVerificationReceipt {
status: "VERIFIED",
reason: None, reason: None,
source_commit: Some(source_commit), source_commit,
signer_count: 0, signer_id: Some(receipt.signer_id),
authorization_enabled: false, authorization_enabled: true,
} }
} }
fn native_client() -> Result<Client, ()> {
Client::builder()
.connect_timeout(Duration::from_secs(8))
.timeout(Duration::from_secs(15))
.redirect(reqwest::redirect::Policy::none())
.build()
.map_err(|_| ())
}
#[tauri::command] #[tauri::command]
pub async fn load_persona_control_authorization_registry( pub async fn load_persona_control_authorization_registry(
) -> NativePersonaAuthorizationRegistryReceipt { ) -> NativePersonaAuthorizationRegistryReceipt {
let client = match Client::builder() let client = match native_client() {
.connect_timeout(Duration::from_secs(8))
.timeout(Duration::from_secs(15))
.redirect(reqwest::redirect::Policy::none())
.build()
{
Ok(client) => client, Ok(client) => client,
Err(_) => return unavailable("CLIENT_UNAVAILABLE"), Err(()) => return unavailable("CLIENT_UNAVAILABLE"),
}; };
load_with_urls(&client, ANCHOR_URL, CODE_URL).await load_with_urls(&client, ANCHOR_URL, CODE_URL).await
} }
#[tauri::command]
pub async fn verify_persona_control_authorization(
receipt: Value,
expectation: NativePersonaAuthorizationExpectation,
) -> NativePersonaAuthorizationVerificationReceipt {
let client = match native_client() {
Ok(client) => client,
Err(()) => return denied("CLIENT_UNAVAILABLE", None),
};
let snapshot = match load_snapshot(&client, ANCHOR_URL, CODE_URL).await {
Ok(snapshot) => snapshot,
Err(reason) => return denied(reason, None),
};
verify_receipt(receipt, &expectation, snapshot)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{ use super::{
load_with_urls, parse_anchor, registry_url, validate_empty_registry, REGISTRY_PATH, load_snapshot, load_with_urls, parse_anchor, parse_registry, registry_url, signing_bytes,
verify_receipt, LoadedRegistry, NativePersonaAuthorizationExpectation,
PersonaAuthorizationReceipt, REGISTRY_PATH,
}; };
use base64::{engine::general_purpose, Engine as _};
use reqwest::Client; use reqwest::Client;
use ring::rand::SystemRandom;
use ring::signature::{Ed25519KeyPair, KeyPair};
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::net::TcpListener; use std::net::TcpListener;
@ -178,14 +486,12 @@ mod tests {
"branch": "main", "branch": "main",
"public_entry": anchor_url, "public_entry": anchor_url,
"code_entry": code_url, "code_entry": code_url,
"maps": { "maps": { "persona_control_authorization_signers": {
"persona_control_authorization_signers": { "path": REGISTRY_PATH,
"path": REGISTRY_PATH, "id": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001",
"id": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001", "schema": "gh-aios.persona-control-authorization-signers/v1",
"schema": "gh-aios.persona-control-authorization-signers/v1", "state": "CURRENT_EMPTY_NO_TRUSTED_SIGNER"
"state": "CURRENT_EMPTY_NO_TRUSTED_SIGNER" }},
}
},
"navigation_source": { "navigation_source": {
"anchor_id": "GLW-PUBLIC-NAV-ANCHOR-001", "anchor_id": "GLW-PUBLIC-NAV-ANCHOR-001",
"source_commit": COMMIT, "source_commit": COMMIT,
@ -195,22 +501,88 @@ mod tests {
}) })
} }
fn empty_registry() -> Value { fn registry(signers: Value) -> Value {
json!({ json!({
"schema": "gh-aios.persona-control-authorization-signers/v1", "schema": "gh-aios.persona-control-authorization-signers/v1",
"registryId": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001", "registryId": "GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001",
"state": "CURRENT", "state": "CURRENT",
"signers": [] "signers": signers
}) })
} }
fn serve_snapshot() -> (String, String) { fn public_key_pem(pair: &Ed25519KeyPair) -> String {
let mut der = super::ED25519_SPKI_PREFIX.to_vec();
der.extend_from_slice(pair.public_key().as_ref());
format!(
"-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----\n",
general_purpose::STANDARD.encode(der)
)
}
fn signer(pair: &Ed25519KeyPair, status: &str) -> Value {
json!({
"algorithm": "Ed25519",
"humanResponsibilitySubjects": ["BINGSHUO"],
"personaIds": ["ICE-P-ZY001"],
"publicKeyPem": public_key_pem(pair),
"scopes": ["PERSONA_PRIMARY_LANGUAGE_PLANNING"],
"signerId": "GH-AIOS-AUTHORIZER-001",
"status": status
})
}
fn unsigned_receipt() -> PersonaAuthorizationReceipt {
PersonaAuthorizationReceipt {
schema: "hololake.persona-control-authorization/v2".into(),
outcome: "VERIFIED".into(),
authorization_id: "AUTH-001".into(),
verifier: "GUANGHU_OS".into(),
scope: "PERSONA_PRIMARY_LANGUAGE_PLANNING".into(),
persona_id: "ICE-P-ZY001".into(),
human_responsibility_subject: "BINGSHUO".into(),
repository_head: "a".repeat(40),
model_instance_id: "MODEL-INSTANCE-001".into(),
request_id: "REQ-001".into(),
source_language_anchor: "HLP-CURRENT-ARCH-001@2026-08-12.14".into(),
issued_at: "2026-08-12T05:00:00+08:00".into(),
valid_until: "2026-08-12T05:10:00+08:00".into(),
evidence_digest: "b".repeat(64),
signer_id: "GH-AIOS-AUTHORIZER-001".into(),
signature_algorithm: "Ed25519".into(),
signature: String::new(),
}
}
fn expectation() -> NativePersonaAuthorizationExpectation {
NativePersonaAuthorizationExpectation {
persona_id: "ICE-P-ZY001".into(),
human_responsibility_subject: "BINGSHUO".into(),
repository_head: "a".repeat(40),
model_instance_id: "MODEL-INSTANCE-001".into(),
request_id: "REQ-001".into(),
source_language_anchor: "HLP-CURRENT-ARCH-001@2026-08-12.14".into(),
observed_at_milliseconds: chrono::DateTime::parse_from_rfc3339(
"2026-08-12T05:05:00+08:00",
)
.unwrap()
.timestamp_millis(),
}
}
fn signed_receipt(pair: &Ed25519KeyPair) -> Value {
let mut receipt = unsigned_receipt();
receipt.signature = general_purpose::URL_SAFE_NO_PAD
.encode(pair.sign(&signing_bytes(&receipt).unwrap()).as_ref());
serde_json::to_value(receipt).unwrap()
}
fn serve_snapshot(registry_body: Value) -> (String, String) {
let listener = TcpListener::bind("127.0.0.1:0").expect("bind test snapshot server"); let listener = TcpListener::bind("127.0.0.1:0").expect("bind test snapshot server");
let address = listener.local_addr().expect("snapshot server address"); let address = listener.local_addr().expect("snapshot server address");
let anchor_url = format!("http://{address}/anchor"); let anchor_url = format!("http://{address}/anchor");
let code_url = format!("http://{address}/code"); let code_url = format!("http://{address}/code");
let anchor_body = anchor(&anchor_url, &code_url).to_string(); let anchor_body = anchor(&anchor_url, &code_url).to_string();
let registry_body = empty_registry().to_string(); let registry_body = registry_body.to_string();
thread::spawn(move || { thread::spawn(move || {
for _ in 0..2 { for _ in 0..2 {
let (mut stream, _) = listener.accept().expect("accept snapshot request"); let (mut stream, _) = listener.accept().expect("accept snapshot request");
@ -223,19 +595,19 @@ mod tests {
assert!(request.contains(&format!("/raw/commit/{COMMIT}/{REGISTRY_PATH}"))); assert!(request.contains(&format!("/raw/commit/{COMMIT}/{REGISTRY_PATH}")));
&registry_body &registry_body
}; };
write!( 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()).unwrap();
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) (anchor_url, code_url)
} }
fn key_pair() -> Ed25519KeyPair {
let pkcs8 = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap()
}
#[test] #[test]
fn exact_anchor_and_registry_are_bound_to_one_commit() { fn exact_anchor_and_empty_registry_are_bound_to_one_commit() {
let anchor_url = "https://example.test/anchor"; let anchor_url = "https://example.test/anchor";
let code_url = "https://example.test/code"; let code_url = "https://example.test/code";
assert_eq!( assert_eq!(
@ -246,11 +618,14 @@ mod tests {
registry_url(code_url, COMMIT), registry_url(code_url, COMMIT),
format!("{code_url}/raw/commit/{COMMIT}/{REGISTRY_PATH}") format!("{code_url}/raw/commit/{COMMIT}/{REGISTRY_PATH}")
); );
assert_eq!(validate_empty_registry(&empty_registry()), Ok(())); assert!(parse_registry(registry(json!([])))
.unwrap()
.signers
.is_empty());
} }
#[test] #[test]
fn degraded_or_changed_sources_and_nonempty_registries_fail_closed() { fn degraded_changed_or_malformed_sources_fail_closed() {
let anchor_url = "https://example.test/anchor"; let anchor_url = "https://example.test/anchor";
let code_url = "https://example.test/code"; let code_url = "https://example.test/code";
let mut degraded = anchor(anchor_url, code_url); let mut degraded = anchor(anchor_url, code_url);
@ -266,39 +641,120 @@ mod tests {
parse_anchor(&wrong_path, anchor_url, code_url), parse_anchor(&wrong_path, anchor_url, code_url),
Err("ANCHOR_INVALID") Err("ANCHOR_INVALID")
); );
let mut nonempty = empty_registry(); assert_eq!(parse_registry(json!([])).unwrap_err(), "REGISTRY_INVALID");
nonempty["signers"] = json!([{"signerId": "UNVERIFIED"}]);
assert_eq!( assert_eq!(
validate_empty_registry(&nonempty), parse_registry(registry(json!([{"signerId":"UNKNOWN"}]))).unwrap_err(),
Err("TRUSTED_SIGNER_REQUIRES_NATIVE_VERIFIER") "REGISTRY_INVALID"
);
}
#[test]
fn native_ed25519_verifier_accepts_only_exact_current_scoped_evidence() {
let pair = key_pair();
let snapshot = LoadedRegistry {
source_commit: COMMIT.into(),
registry: parse_registry(registry(json!([signer(&pair, "ACTIVE")]))).unwrap(),
};
let verified = serde_json::to_value(verify_receipt(
signed_receipt(&pair),
&expectation(),
snapshot,
))
.unwrap();
assert_eq!(verified["status"], "VERIFIED");
assert_eq!(verified["authorizationEnabled"], true);
assert_eq!(verified["sourceCommit"], COMMIT);
let empty = LoadedRegistry {
source_commit: COMMIT.into(),
registry: parse_registry(registry(json!([]))).unwrap(),
};
let denied =
serde_json::to_value(verify_receipt(signed_receipt(&pair), &expectation(), empty))
.unwrap();
assert_eq!(denied["reason"], "NO_TRUSTED_SIGNER");
assert_eq!(denied["authorizationEnabled"], false);
}
#[test]
fn tampering_revocation_time_and_signature_fail_closed() {
let pair = key_pair();
let other = key_pair();
let registry_for = |status| LoadedRegistry {
source_commit: COMMIT.into(),
registry: parse_registry(registry(json!([signer(&pair, status)]))).unwrap(),
};
let mut tampered = signed_receipt(&pair);
tampered["requestId"] = json!("REQ-OTHER");
assert_eq!(
serde_json::to_value(verify_receipt(
tampered,
&expectation(),
registry_for("ACTIVE")
))
.unwrap()["reason"],
"RECEIPT_BINDING_INVALID"
);
assert_eq!(
serde_json::to_value(verify_receipt(
signed_receipt(&pair),
&expectation(),
registry_for("REVOKED")
))
.unwrap()["reason"],
"NO_TRUSTED_SIGNER"
);
assert_eq!(
serde_json::to_value(verify_receipt(
signed_receipt(&other),
&expectation(),
registry_for("ACTIVE")
))
.unwrap()["reason"],
"SIGNATURE_INVALID"
);
let mut stale = expectation();
stale.observed_at_milliseconds += 600_001;
assert_eq!(
serde_json::to_value(verify_receipt(
signed_receipt(&pair),
&stale,
registry_for("ACTIVE")
))
.unwrap()["reason"],
"RECEIPT_BINDING_INVALID"
); );
assert_eq!(validate_empty_registry(&json!([])), Err("REGISTRY_INVALID"));
} }
#[tokio::test] #[tokio::test]
async fn native_transport_loads_the_exact_empty_registry_without_enabling_authorization() { async fn native_transport_loads_the_exact_empty_registry_without_enabling_authorization() {
let (anchor_url, code_url) = serve_snapshot(); let (anchor_url, code_url) = serve_snapshot(registry(json!([])));
let client = Client::builder().build().expect("test client"); let client = Client::builder().build().unwrap();
let receipt = load_with_urls(&client, &anchor_url, &code_url).await; let receipt =
let projection = serde_json::to_value(receipt).expect("serialize receipt"); serde_json::to_value(load_with_urls(&client, &anchor_url, &code_url).await).unwrap();
assert_eq!(projection["status"], "CURRENT_EMPTY"); assert_eq!(receipt["status"], "CURRENT_EMPTY");
assert_eq!(projection["sourceCommit"], COMMIT); assert_eq!(receipt["sourceCommit"], COMMIT);
assert_eq!(projection["signerCount"], 0); assert_eq!(receipt["signerCount"], 0);
assert_eq!(projection["authorizationEnabled"], false); assert_eq!(receipt["authorizationEnabled"], false);
assert!(projection["reason"].is_null());
} }
#[tokio::test] #[tokio::test]
async fn source_and_anchor_failures_return_bounded_unavailable_receipts() { async fn source_failures_return_bounded_unavailable_receipts() {
let listener = TcpListener::bind("127.0.0.1:0").expect("reserve test port"); let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let unavailable = format!("http://{}/anchor", listener.local_addr().unwrap()); let unavailable = format!("http://{}/anchor", listener.local_addr().unwrap());
drop(listener); drop(listener);
let client = Client::builder().build().expect("test client"); let client = Client::builder().build().unwrap();
let source_failure = let failure =
serde_json::to_value(load_with_urls(&client, &unavailable, "http://unused").await) serde_json::to_value(load_with_urls(&client, &unavailable, "http://unused").await)
.unwrap(); .unwrap();
assert_eq!(source_failure["status"], "UNAVAILABLE"); assert_eq!(failure["status"], "UNAVAILABLE");
assert_eq!(source_failure["reason"], "SOURCE_UNAVAILABLE"); assert_eq!(failure["reason"], "SOURCE_UNAVAILABLE");
assert_eq!(source_failure["authorizationEnabled"], false); assert_eq!(failure["authorizationEnabled"], false);
assert_eq!(
load_snapshot(&client, &unavailable, "http://unused")
.await
.err(),
Some("SOURCE_UNAVAILABLE")
);
} }
} }

View file

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

View file

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

View file

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

View file

@ -174,6 +174,7 @@
"persona_control_authorization_cryptographic_provenance_source_implemented": 100, "persona_control_authorization_cryptographic_provenance_source_implemented": 100,
"persona_control_authorization_registry_source_published": 100, "persona_control_authorization_registry_source_published": 100,
"native_persona_control_authorization_registry_loader_source_integrated": 100, "native_persona_control_authorization_registry_loader_source_integrated": 100,
"native_persona_control_authorization_receipt_verifier_source_integrated": 100,
"persona_control_authorization_trusted_signer_registered": 0, "persona_control_authorization_trusted_signer_registered": 0,
"desktop_language_entry_source_integrated": 100, "desktop_language_entry_source_integrated": 100,
"persona_control_authorization_runtime_integrated": 0, "persona_control_authorization_runtime_integrated": 0,

View file

@ -14,7 +14,7 @@ const age = readJson("routing/hololake-age-runtime-architecture.json");
const rules = readJson("routing/hololake-engineering-rules.json"); const rules = readJson("routing/hololake-engineering-rules.json");
test("B0 is restored before product organs and remains resident in every cognition step", () => { test("B0 is restored before product organs and remains resident in every cognition step", () => {
assert.equal(architecture.version, "2026-08-12.13"); assert.equal(architecture.version, "2026-08-12.14");
assert.equal( assert.equal(
architecture.read_order[1], architecture.read_order[1],
architecture.cognitive_gravity_and_continuity.architecture_page, architecture.cognitive_gravity_and_continuity.architecture_page,
@ -100,6 +100,7 @@ test("the first implementation stage is one vertical AGE loop, not Mirror parall
assert.equal(gravity.truth.persona_control_authorization_cryptographic_provenance_source_implemented, 100); assert.equal(gravity.truth.persona_control_authorization_cryptographic_provenance_source_implemented, 100);
assert.equal(gravity.truth.persona_control_authorization_registry_source_published, 100); assert.equal(gravity.truth.persona_control_authorization_registry_source_published, 100);
assert.equal(gravity.truth.native_persona_control_authorization_registry_loader_source_integrated, 100); assert.equal(gravity.truth.native_persona_control_authorization_registry_loader_source_integrated, 100);
assert.equal(gravity.truth.native_persona_control_authorization_receipt_verifier_source_integrated, 100);
assert.equal(gravity.truth.persona_control_authorization_trusted_signer_registered, 0); assert.equal(gravity.truth.persona_control_authorization_trusted_signer_registered, 0);
assert.equal(gravity.truth.desktop_language_entry_source_integrated, 100); assert.equal(gravity.truth.desktop_language_entry_source_integrated, 100);
assert.equal(gravity.truth.persona_control_authorization_runtime_integrated, 0); assert.equal(gravity.truth.persona_control_authorization_runtime_integrated, 0);
@ -109,7 +110,8 @@ test("the first implementation stage is one vertical AGE loop, not Mirror parall
assert.equal(architecture.interaction_model.source_contract.ed25519_verification_source_implemented, true); assert.equal(architecture.interaction_model.source_contract.ed25519_verification_source_implemented, true);
assert.equal(architecture.interaction_model.source_contract.trusted_signer_source_registered, false); assert.equal(architecture.interaction_model.source_contract.trusted_signer_source_registered, false);
assert.equal(architecture.interaction_model.source_contract.native_authorization_registry_loader_integrated, true); assert.equal(architecture.interaction_model.source_contract.native_authorization_registry_loader_integrated, true);
assert.equal(architecture.interaction_model.source_contract.native_authorization_receipt_loader_integrated, false); assert.equal(architecture.interaction_model.source_contract.native_authorization_receipt_loader_integrated, true);
assert.equal(architecture.interaction_model.source_contract.native_authorization_verifier_wired_to_language_controller, false);
assert.equal(gravity.truth.real_persona_repository_manifest_bound, 0); assert.equal(gravity.truth.real_persona_repository_manifest_bound, 0);
assert.equal(gravity.truth.natural_language_partner_adapter_runtime_integrated, 0); assert.equal(gravity.truth.natural_language_partner_adapter_runtime_integrated, 0);
assert.equal(gravity.truth.single_age_vertical_loop_implemented, 0); assert.equal(gravity.truth.single_age_vertical_loop_implemented, 0);

View file

@ -1,7 +1,7 @@
{ {
"schema": "hololake.current-architecture/v1", "schema": "hololake.current-architecture/v1",
"architecture_id": "HLP-CURRENT-ARCH-001", "architecture_id": "HLP-CURRENT-ARCH-001",
"version": "2026-08-12.13", "version": "2026-08-12.14",
"state": "CURRENT_CANONICAL", "state": "CURRENT_CANONICAL",
"product": { "product": {
"formal_name": "光湖语言系统 · 通用人工智能操作平台", "formal_name": "光湖语言系统 · 通用人工智能操作平台",
@ -120,6 +120,7 @@
"persona_control_authorization_cryptographic_provenance_source_implemented": true, "persona_control_authorization_cryptographic_provenance_source_implemented": true,
"persona_control_authorization_registry_source_published": true, "persona_control_authorization_registry_source_published": true,
"native_persona_control_authorization_registry_loader_source_integrated": true, "native_persona_control_authorization_registry_loader_source_integrated": true,
"native_persona_control_authorization_receipt_verifier_source_integrated": true,
"persona_control_authorization_trusted_signer_registered": false, "persona_control_authorization_trusted_signer_registered": false,
"desktop_language_entry_source_integrated": true, "desktop_language_entry_source_integrated": true,
"persona_control_authorization_runtime_integrated": false, "persona_control_authorization_runtime_integrated": false,
@ -407,7 +408,8 @@
"ed25519_verification_source_implemented": true, "ed25519_verification_source_implemented": true,
"trusted_signer_source_registered": false, "trusted_signer_source_registered": false,
"native_authorization_registry_loader_integrated": true, "native_authorization_registry_loader_integrated": true,
"native_authorization_receipt_loader_integrated": false, "native_authorization_receipt_loader_integrated": true,
"native_authorization_verifier_wired_to_language_controller": false,
"partner_deliberation_required": true, "partner_deliberation_required": true,
"human_utterance_is_direct_command": false, "human_utterance_is_direct_command": false,
"allowed_dispositions": [ "allowed_dispositions": [
@ -419,7 +421,7 @@
"focused_tests": "11_OF_11_PASS", "focused_tests": "11_OF_11_PASS",
"authorization_focused_tests": "13_OF_13_PASS", "authorization_focused_tests": "13_OF_13_PASS",
"authorization_cryptographic_tests": "7_OF_7_PASS", "authorization_cryptographic_tests": "7_OF_7_PASS",
"native_authorization_registry_loader_tests": "4_OF_4_PASS" "native_authorization_registry_and_receipt_verifier_tests": "6_OF_6_PASS"
}, },
"ui_plugin_system": { "ui_plugin_system": {
"record_id": "HLP-HOT-PLUGGABLE-UI-001", "record_id": "HLP-HOT-PLUGGABLE-UI-001",