feat(hololake): implement GHS-014 stage-one home

This commit is contained in:
冰朔 2026-08-13 13:52:59 +08:00
commit 4d2415a35b
15 changed files with 891 additions and 49 deletions

View file

@ -27,7 +27,7 @@ use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::{Path, PathBuf};
use std::sync::{
atomic::{AtomicBool, Ordering},
atomic::{AtomicBool, AtomicUsize, Ordering},
mpsc, Arc,
};
use std::thread;
@ -40,10 +40,33 @@ const MAX_REQUEST_BYTES: u64 = 1024 * 1024;
pub struct DirectLocalBrokerHandle {
shutdown: Arc<AtomicBool>,
authenticated_connections: Arc<AtomicUsize>,
socket_path: PathBuf,
worker: Option<thread::JoinHandle<()>>,
}
impl DirectLocalBrokerHandle {
pub fn active_connection_count(&self) -> usize {
self.authenticated_connections.load(Ordering::Acquire)
}
}
struct AuthenticatedConnectionGuard(Arc<AtomicUsize>);
impl Drop for AuthenticatedConnectionGuard {
fn drop(&mut self) {
self.0.fetch_sub(1, Ordering::AcqRel);
}
}
struct BrokerStorageRoots {
session: PathBuf,
routing: PathBuf,
pncc_mount: PathBuf,
pncc_remote: PathBuf,
pncc_projection: PathBuf,
}
impl Drop for DirectLocalBrokerHandle {
fn drop(&mut self) {
self.shutdown.store(true, Ordering::Release);
@ -296,7 +319,9 @@ fn start_at(
)?;
let shutdown = Arc::new(AtomicBool::new(false));
let authenticated_connections = Arc::new(AtomicUsize::new(0));
let worker_shutdown = Arc::clone(&shutdown);
let worker_authenticated_connections = Arc::clone(&authenticated_connections);
let worker_socket = socket_path.clone();
let (ready_sender, ready_receiver) = mpsc::sync_channel(1);
let worker = thread::Builder::new()
@ -305,12 +330,15 @@ fn start_at(
let _ = ready_sender.send(());
serve(
listener,
session_root,
routing_root,
pncc_mount_root,
pncc_remote_root,
pncc_projection_root,
BrokerStorageRoots {
session: session_root,
routing: routing_root,
pncc_mount: pncc_mount_root,
pncc_remote: pncc_remote_root,
pncc_projection: pncc_projection_root,
},
&worker_shutdown,
&worker_authenticated_connections,
);
let _ = fs::remove_file(worker_socket);
})
@ -321,6 +349,7 @@ fn start_at(
Ok(DirectLocalBrokerHandle {
shutdown,
authenticated_connections,
socket_path,
worker: Some(worker),
})
@ -328,12 +357,9 @@ fn start_at(
fn serve(
listener: UnixListener,
session_root: PathBuf,
routing_root: PathBuf,
pncc_mount_root: PathBuf,
pncc_remote_root: PathBuf,
pncc_projection_root: PathBuf,
roots: BrokerStorageRoots,
shutdown: &AtomicBool,
authenticated_connections: &Arc<AtomicUsize>,
) {
while !shutdown.load(Ordering::Acquire) {
match listener.accept() {
@ -341,11 +367,15 @@ fn serve(
if shutdown.load(Ordering::Acquire) {
break;
}
let root = session_root.clone();
let routes = routing_root.clone();
let pncc_mounts = pncc_mount_root.clone();
let pncc_remote = pncc_remote_root.clone();
let pncc_projection = pncc_projection_root.clone();
if stream.set_nonblocking(false).is_err() {
continue;
}
let root = roots.session.clone();
let routes = roots.routing.clone();
let pncc_mounts = roots.pncc_mount.clone();
let pncc_remote = roots.pncc_remote.clone();
let pncc_projection = roots.pncc_projection.clone();
let client_authenticated_connections = Arc::clone(authenticated_connections);
let _ = thread::Builder::new()
.name("hololake-direct-local-client".into())
.spawn(move || {
@ -356,6 +386,7 @@ fn serve(
&pncc_mounts,
&pncc_remote,
&pncc_projection,
client_authenticated_connections,
)
});
}
@ -374,12 +405,14 @@ fn serve_connection(
pncc_mount_root: &Path,
pncc_remote_root: &Path,
pncc_projection_root: &Path,
authenticated_connections: Arc<AtomicUsize>,
) {
let read_stream = match stream.try_clone() {
Ok(stream) => stream,
Err(_) => return,
};
let mut reader = BufReader::new(read_stream);
let mut authenticated_connection = None;
loop {
let mut bytes = Vec::new();
let read = match reader
@ -393,6 +426,7 @@ fn serve_connection(
if read == 0 {
return;
}
let request_authenticates = request_establishes_authenticated_connection(&bytes);
let response = if read as u64 > MAX_REQUEST_BYTES || !bytes.ends_with(b"\n") {
BrokerResponse::error("HOLOLAKE_BROKER_REQUEST_TOO_LARGE")
} else {
@ -405,6 +439,12 @@ fn serve_connection(
&bytes[..bytes.len() - 1],
)
};
if response.ok && request_authenticates && authenticated_connection.is_none() {
authenticated_connections.fetch_add(1, Ordering::AcqRel);
authenticated_connection = Some(AuthenticatedConnectionGuard(Arc::clone(
&authenticated_connections,
)));
}
if serde_json::to_writer(&mut stream, &response).is_err()
|| stream.write_all(b"\n").is_err()
|| stream.flush().is_err()
@ -417,6 +457,18 @@ fn serve_connection(
}
}
fn request_establishes_authenticated_connection(bytes: &[u8]) -> bool {
serde_json::from_slice::<serde_json::Value>(bytes)
.ok()
.and_then(|value| {
value
.get("operation")
.and_then(Value::as_str)
.map(str::to_owned)
})
.is_some_and(|operation| matches!(operation.as_str(), "OPEN_SESSION" | "RESUME_SESSION"))
}
fn dispatch(
session_root: &Path,
routing_root: &Path,
@ -840,4 +892,68 @@ mod tests {
assert_eq!(verified["result"]["emptyMeansOffline"], false);
assert_eq!(verified["result"]["returnedEventCount"], 0);
}
#[test]
fn only_an_authenticated_persistent_connector_counts_as_online() {
let temp = TempDir::new().unwrap();
let socket = temp.path().join("runtime/broker.sock");
let descriptor = temp.path().join("broker.json");
let sessions = temp.path().join("sessions");
let routes = temp.path().join("routes");
fs::create_dir_all(&sessions).unwrap();
fs::create_dir_all(&routes).unwrap();
let broker = start_at(sessions.clone(), routes, descriptor, socket.clone()).unwrap();
let probe = UnixStream::connect(&socket).unwrap();
thread::sleep(Duration::from_millis(50));
assert_eq!(broker.active_connection_count(), 0);
drop(probe);
let ticket = issue_ticket_at(
&sessions,
IssueDiscoveryTicketInput {
account_id: "human-1".into(),
lane_id: "DEV-1".into(),
client_instance_id: "codex-1".into(),
},
)
.unwrap();
let mut connector = UnixStream::connect(&socket).unwrap();
serde_json::to_writer(
&mut connector,
&serde_json::json!({
"operation": "OPEN_SESSION",
"input": {
"accountId": "human-1",
"laneId": "DEV-1",
"clientInstanceId": "codex-1",
"discoveryTicket": ticket.discovery_ticket
}
}),
)
.unwrap();
connector.write_all(b"\n").unwrap();
assert!(request_establishes_authenticated_connection(
br#"{"operation":"OPEN_SESSION"}"#
));
let mut response = String::new();
let mut response_reader = BufReader::new(connector.try_clone().unwrap());
response_reader.read_line(&mut response).unwrap();
assert_eq!(
serde_json::from_str::<Value>(&response).unwrap()["ok"],
true
);
thread::sleep(Duration::from_millis(50));
assert_eq!(broker.active_connection_count(), 1);
drop(connector);
drop(response_reader);
for _ in 0..20 {
if broker.active_connection_count() == 0 {
break;
}
thread::sleep(Duration::from_millis(10));
}
assert_eq!(broker.active_connection_count(), 0);
}
}

View file

@ -203,6 +203,43 @@ pub(crate) fn direct_session_root(app: &AppHandle) -> Result<PathBuf, String> {
.map_err(|error| format!("HOLOLAKE_DIRECT_SESSION_STORAGE_UNAVAILABLE: {error}"))
}
pub(crate) fn active_session_count_at(root: &Path) -> Result<usize, String> {
let accounts = root.join("accounts");
if !accounts.exists() {
return Ok(0);
}
let mut count = 0;
for entry in fs::read_dir(&accounts)
.map_err(|error| format!("HOLOLAKE_DIRECT_SESSION_STORAGE_UNAVAILABLE: {error}"))?
{
let entry = entry
.map_err(|error| format!("HOLOLAKE_DIRECT_SESSION_STORAGE_UNAVAILABLE: {error}"))?;
let kind = entry
.file_type()
.map_err(|error| format!("HOLOLAKE_DIRECT_SESSION_STORAGE_UNAVAILABLE: {error}"))?;
if !kind.is_dir() || kind.is_symlink() {
continue;
}
let active_path = entry.path().join("active-session.json");
if !active_path.exists() {
continue;
}
let metadata = fs::symlink_metadata(&active_path)
.map_err(|error| format!("HOLOLAKE_ACTIVE_SESSION_INVALID: {error}"))?;
if !metadata.file_type().is_file() || metadata.file_type().is_symlink() {
return Err("HOLOLAKE_ACTIVE_SESSION_INVALID".into());
}
let active: ActiveSessionRecord = read_json(&active_path, "ACTIVE_SESSION")?;
if active.schema != SESSION_SCHEMA
|| active.account_key != entry.file_name().to_string_lossy()
{
return Err("HOLOLAKE_ACTIVE_SESSION_INVALID".into());
}
count += 1;
}
Ok(count)
}
pub(crate) fn open_at(
root: &Path,
input: OpenSessionInput,

View file

@ -0,0 +1,44 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use crate::direct_local_broker::DirectLocalBrokerHandle;
use crate::direct_local_session::{active_session_count_at, direct_session_root};
use crate::pncc_receipt_projection::{pncc_projection_root, projection_event_count_at};
use crate::pncc_repository_binding::{mounted_repository_count_at, pncc_repository_mount_root};
use crate::release_trust::release_trust_state;
use serde::Serialize;
use tauri::{AppHandle, State};
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct HoloLakeHomeStatus {
pub schema: &'static str,
pub direct_local_broker_state: &'static str,
pub direct_connection_count: usize,
pub resumable_session_count: usize,
pub code_repository_mount_count: usize,
pub pncc_receipt_count: usize,
pub update_state: &'static str,
pub automatic_upstream_updates: bool,
pub mcp_role: &'static str,
}
#[tauri::command]
pub fn get_hololake_home_status(
app: AppHandle,
broker: State<'_, DirectLocalBrokerHandle>,
) -> Result<HoloLakeHomeStatus, String> {
let session_root = direct_session_root(&app)?;
let mount_root = pncc_repository_mount_root(&app)?;
let projection_root = pncc_projection_root(&app)?;
Ok(HoloLakeHomeStatus {
schema: "hololake.home-status/v1",
direct_local_broker_state: "READY",
direct_connection_count: broker.active_connection_count(),
resumable_session_count: active_session_count_at(&session_root)?,
code_repository_mount_count: mounted_repository_count_at(&mount_root)?,
pncc_receipt_count: projection_event_count_at(&projection_root)?,
update_state: release_trust_state()?,
automatic_upstream_updates: false,
mcp_role: "DISCOVERY_RECOVERY_COMPATIBILITY_ONLY",
})
}

View file

@ -1,6 +1,7 @@
mod direct_local_broker;
mod direct_local_session;
mod dynamic_capability_routing;
mod home_status;
mod local_development_bridge;
mod pncc_receipt_projection;
mod pncc_remote_git;
@ -17,6 +18,7 @@ pub fn run_connector() -> Result<(), String> {
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
home_status::get_hololake_home_status,
direct_local_session::issue_direct_local_discovery_ticket,
direct_local_session::open_direct_local_session,
direct_local_session::resume_direct_local_session,

View file

@ -102,6 +102,11 @@ pub(crate) fn pncc_projection_root(app: &AppHandle) -> Result<PathBuf, String> {
.map_err(|error| format!("PNCC_PROJECTION_STORAGE_UNAVAILABLE: {error}"))
}
pub(crate) fn projection_event_count_at(root: &Path) -> Result<usize, String> {
let _lock = lock_projection(root)?;
Ok(load_and_verify(root)?.events.len())
}
pub(crate) fn append_repository_binding_at(
root: &Path,
receipt: &PnccRepositoryMountReceipt,

View file

@ -77,6 +77,32 @@ pub(crate) fn pncc_repository_mount_root(app: &AppHandle) -> Result<PathBuf, Str
.map_err(|error| format!("PNCC_REPOSITORY_MOUNT_STORAGE_UNAVAILABLE: {error}"))
}
pub(crate) fn mounted_repository_count_at(root: &Path) -> Result<usize, String> {
let mut count = 0;
for entry in fs::read_dir(root)
.map_err(|error| format!("PNCC_REPOSITORY_MOUNT_STORAGE_UNAVAILABLE: {error}"))?
{
let entry =
entry.map_err(|error| format!("PNCC_REPOSITORY_MOUNT_STORAGE_UNAVAILABLE: {error}"))?;
let kind = entry
.file_type()
.map_err(|error| format!("PNCC_REPOSITORY_MOUNT_STORAGE_UNAVAILABLE: {error}"))?;
let path = entry.path();
if kind.is_symlink()
|| !kind.is_file()
|| path.extension().and_then(|value| value.to_str()) != Some("json")
{
continue;
}
let record: PnccRepositoryMountRecord = read_json(&path)?;
if record.schema != MOUNT_SCHEMA || mount_path(root, &record.mount_id) != path {
return Err("PNCC_REPOSITORY_MOUNT_RECORD_INVALID".into());
}
count += 1;
}
Ok(count)
}
pub(crate) fn inspect_mounted_at(
root: &Path,
input: InspectMountedPnccRepositoryInput,

View file

@ -33,6 +33,13 @@ enum ValidatedReleaseTrust {
},
}
pub(crate) fn release_trust_state() -> Result<&'static str, String> {
match validate_release_trust(EMBEDDED_RELEASE_TRUST)? {
ValidatedReleaseTrust::Disabled => Ok("UNPROVISIONED_FAIL_CLOSED"),
ValidatedReleaseTrust::Enabled { .. } => Ok("READY_HUMAN_CONFIRMATION_REQUIRED"),
}
}
fn validate_release_trust(raw: &str) -> Result<ValidatedReleaseTrust, String> {
let trust: ReleaseTrust = serde_json::from_str(raw)
.map_err(|error| format!("invalid HoloLake release trust document: {error}"))?;