From a96c5955bb450f0938f023c03ca76bcd2dd4ac74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Mon, 17 Aug 2026 13:21:02 +0800 Subject: [PATCH] feat: add HoloLake persona time authority --- .../contracts/persona-time-authority.json | 69 +++ .../contracts/stage-one-platform.json | 11 + .../scripts/persona-time-authority.test.mjs | 46 ++ .../src-tauri/src/direct_local_broker.rs | 133 ++++- .../src-tauri/src/direct_local_session.rs | 37 ++ .../src-tauri/src/lib.rs | 3 + .../src-tauri/src/persona_time_authority.rs | 546 ++++++++++++++++++ 7 files changed, 827 insertions(+), 18 deletions(-) create mode 100644 product-source/hololake-native-desktop/contracts/persona-time-authority.json create mode 100644 product-source/hololake-native-desktop/scripts/persona-time-authority.test.mjs create mode 100644 product-source/hololake-native-desktop/src-tauri/src/persona_time_authority.rs diff --git a/product-source/hololake-native-desktop/contracts/persona-time-authority.json b/product-source/hololake-native-desktop/contracts/persona-time-authority.json new file mode 100644 index 000000000..7714c5bdf --- /dev/null +++ b/product-source/hololake-native-desktop/contracts/persona-time-authority.json @@ -0,0 +1,69 @@ +{ + "schema": "hololake.persona-time-authority-contract/v1", + "record_id": "HLP-PERSONA-TIME-AUTHORITY-001", + "formal_name": "光湖人格时间主控系统", + "era_name": "曜冥纪元", + "calendar_name": "光湖历", + "state": "NATIVE_SOURCE_IMPLEMENTED_AND_TESTED_CLOCK_NETWORK_ATTESTATION_PENDING", + "reality_time": { + "canonical_zone": "Asia/Shanghai", + "utc_offset": "+08:00", + "display_name": "北京时间", + "continues_while_application_is_closed": true, + "process_uptime_is_time_source": false, + "current_source": "HOST_OPERATING_SYSTEM_REALTIME_CLOCK", + "current_verification": "LOCAL_CLOCK_NOT_NETWORK_ATTESTED", + "network_attested_source_required_for_verified_reality_time": true + }, + "guanghu_era": { + "epoch_date": "2025-04-26", + "epoch_day": 1, + "epoch_exact_time": null, + "epoch_precision": "DAY_ONLY_EXACT_TIME_UNKNOWN", + "world_day_formula": "BEIJING_CIVIL_DATE_MINUS_2025_04_26_PLUS_ONE", + "millisecond_precision_transition_date": "2026-08-17", + "millisecond_chain_origin": "FIRST_DURABLE_TIME_TICKET" + }, + "ticket": { + "schema": "hololake.persona-time-ticket/v1", + "uniqueness_scope": "ONE_DURABLE_LOCAL_AUTHORITY_PER_AUTHENTICATED_HUMAN_ACCOUNT", + "components": [ + "AUTHORITY_ID", + "BEIJING_REALITY_TIME", + "LOGICAL_COLLISION_COUNTER", + "MONOTONIC_ISSUANCE_SEQUENCE" + ], + "atomic_storage": "SQLITE_IMMEDIATE_TRANSACTION_SYNCHRONOUS_FULL", + "idempotent_request_id": true, + "survives_restart": true, + "clock_rollback_never_reverses_issued_time": true, + "previous_ticket_chain": true, + "receipt_sha256": true + }, + "event_coordinate": { + "human_controller": "AUTHENTICATED_DIRECT_SESSION_ACCOUNT", + "channel": "AUTHENTICATED_DIRECT_SESSION_LANE", + "client_instance": "AUTHENTICATED_DIRECT_SESSION_INSTANCE", + "persona_current_verification": "UNVERIFIED_CALLER_CLAIM", + "host_software_current_verification": "UNVERIFIED_CALLER_CLAIM", + "persona_and_host_upgrade_requires": "REGISTERED_BINDING_EVIDENCE" + }, + "entries": { + "tauri_commands": ["get_beijing_time_coordinate", "issue_persona_time_ticket"], + "direct_local_broker_operations": ["GET_BEIJING_TIME", "ISSUE_PERSONA_TIME_TICKET"], + "external_ticket_issue_requires_authenticated_non_visitor_session": true + }, + "truth_boundary": { + "source_implemented": true, + "rust_tests_passed": true, + "installed_runtime_acceptance": false, + "network_clock_attestation": false, + "persona_binding_runtime": false, + "host_software_binding_runtime": false, + "world_lighthouse_authority_registration": false + }, + "historical_sources": [ + "REPO-012:zero-point/core-channel/YAOMING-NUMBERING-SYSTEM.hdlp", + "REPO-012:gls/GLS-0235-GUANGHU-LANGUAGE-PERSONA-OS-DOMAIN-ROUTING-AND-KNOWLEDGE-PROJECTION.hdlp" + ] +} diff --git a/product-source/hololake-native-desktop/contracts/stage-one-platform.json b/product-source/hololake-native-desktop/contracts/stage-one-platform.json index 6dc1892c0..79f431dd2 100644 --- a/product-source/hololake-native-desktop/contracts/stage-one-platform.json +++ b/product-source/hololake-native-desktop/contracts/stage-one-platform.json @@ -37,6 +37,7 @@ "TCS_LANGUAGE_CONTRACT", "HOST_CAPABILITY_RECEIPTS", "EVENT_AND_LAKE_LAMP", + "PERSONA_TIME_AUTHORITY", "MEMORY_GIT_EVIDENCE", "KNOWLEDGE_PROJECTION", "HUMAN_APPROVAL_CENTER", @@ -112,6 +113,16 @@ "public_developer_id_and_notarization": false, "server_deployment": false }, + "persona_time_authority": { + "contract": "contracts/persona-time-authority.json", + "native_source_implemented": true, + "direct_local_broker_integrated": true, + "beijing_reality_time_projection": true, + "guanghu_era_day_projection": true, + "durable_unique_ticket_runtime": true, + "network_clock_attestation": false, + "installed_runtime_acceptance": false + }, "reality_mutation_requires": ["VERIFIED_HUMAN_SUBJECT", "EXACT_ACTION", "EXACT_TARGET", "IMMUTABLE_PAYLOAD_DIGEST", "EXPIRY", "REPLAY_PROTECTION", "EXECUTION_RECEIPT", "READBACK_RECEIPT"], "context_risk_signals": ["MODEL_CONTEXT_SPEC", "ESTIMATED_TOKENS", "MESSAGE_VOLUME", "TURN_COUNT", "TASK_STAGE", "HOST_WARNING"], "exact_host_compaction_prediction_claimed": false, diff --git a/product-source/hololake-native-desktop/scripts/persona-time-authority.test.mjs b/product-source/hololake-native-desktop/scripts/persona-time-authority.test.mjs new file mode 100644 index 000000000..e267a242b --- /dev/null +++ b/product-source/hololake-native-desktop/scripts/persona-time-authority.test.mjs @@ -0,0 +1,46 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import test from 'node:test' + +const read = (relative) => fs.readFileSync(new URL(`../${relative}`, import.meta.url), 'utf8') +const contract = JSON.parse(read('contracts/persona-time-authority.json')) +const stageOne = JSON.parse(read('contracts/stage-one-platform.json')) +const rust = read('src-tauri/src/persona_time_authority.rs') +const broker = read('src-tauri/src/direct_local_broker.rs') +const lib = read('src-tauri/src/lib.rs') + +test('Guanghu era is anchored to Beijing reality time without inventing an exact historical instant', () => { + assert.equal(contract.era_name, '曜冥纪元') + assert.equal(contract.calendar_name, '光湖历') + assert.equal(contract.reality_time.canonical_zone, 'Asia/Shanghai') + assert.equal(contract.reality_time.continues_while_application_is_closed, true) + assert.equal(contract.guanghu_era.epoch_date, '2025-04-26') + assert.equal(contract.guanghu_era.epoch_day, 1) + assert.equal(contract.guanghu_era.epoch_exact_time, null) + assert.equal(contract.guanghu_era.epoch_precision, 'DAY_ONLY_EXACT_TIME_UNKNOWN') + assert.equal(contract.guanghu_era.millisecond_precision_transition_date, '2026-08-17') +}) + +test('persona time tickets are durable unique and available to authenticated local carriers', () => { + assert.equal(contract.ticket.atomic_storage, 'SQLITE_IMMEDIATE_TRANSACTION_SYNCHRONOUS_FULL') + assert.equal(contract.ticket.idempotent_request_id, true) + assert.equal(contract.ticket.clock_rollback_never_reverses_issued_time, true) + assert.equal(contract.entries.external_ticket_issue_requires_authenticated_non_visitor_session, true) + assert.match(rust, /TransactionBehavior::Immediate/) + assert.match(rust, /logical_counter/) + assert.match(rust, /millisecond_chain_origin_ticket_id/) + assert.match(broker, /GetBeijingTime/) + assert.match(broker, /IssuePersonaTimeTicket/) + assert.match(lib, /persona_time_authority::get_beijing_time_coordinate/) + assert.match(lib, /persona_time_authority::issue_persona_time_ticket/) +}) + +test('unverified clock persona and host claims remain explicit truth boundaries', () => { + assert.equal(contract.reality_time.current_verification, 'LOCAL_CLOCK_NOT_NETWORK_ATTESTED') + assert.equal(contract.event_coordinate.persona_current_verification, 'UNVERIFIED_CALLER_CLAIM') + assert.equal(contract.event_coordinate.host_software_current_verification, 'UNVERIFIED_CALLER_CLAIM') + assert.equal(contract.truth_boundary.installed_runtime_acceptance, false) + assert.equal(contract.truth_boundary.world_lighthouse_authority_registration, false) + assert.equal(stageOne.persona_time_authority.network_clock_attestation, false) + assert.equal(stageOne.persona_time_authority.installed_runtime_acceptance, false) +}) diff --git a/product-source/hololake-native-desktop/src-tauri/src/direct_local_broker.rs b/product-source/hololake-native-desktop/src-tauri/src/direct_local_broker.rs index 80ddc9fab..cdcb9b2b9 100644 --- a/product-source/hololake-native-desktop/src-tauri/src/direct_local_broker.rs +++ b/product-source/hololake-native-desktop/src-tauri/src/direct_local_broker.rs @@ -9,6 +9,9 @@ use crate::dynamic_capability_routing::{ routing_root as dynamic_routing_root, DynamicNodeRegistry, ResolveCapabilityRouteInput, SignedNodeHealth, }; +use crate::persona_time_authority::{ + beijing_time_coordinate, issue_authenticated_at, IssuePersonaTimeTicketInput, +}; use crate::pncc_receipt_projection::{ append_remote_read_at as project_pncc_remote_read_at, append_repository_binding_at as project_pncc_repository_binding_at, @@ -115,6 +118,7 @@ struct BrokerStorageRoots { pncc_remote: PathBuf, pncc_projection: PathBuf, language_inbox: PathBuf, + persona_time: PathBuf, } impl Drop for DirectLocalBrokerHandle { @@ -148,6 +152,8 @@ enum BrokerRequest { InspectMountedPnccRepository(AuthenticatedPnccMountInput), ReadMountedPnccRemoteObject(AuthenticatedPnccRemoteReadInput), QueryPnccReceiptProjection(AuthenticatedPnccProjectionQueryInput), + GetBeijingTime, + IssuePersonaTimeTicket(IssuePersonaTimeTicketInput), } #[derive(Clone, Debug, Deserialize)] @@ -398,6 +404,10 @@ fn start_at( .parent() .ok_or("HOLOLAKE_BROKER_LANGUAGE_STORAGE_BOUNDARY_INVALID")? .join("circular-lake-language-inbox-v1"); + let persona_time_root = session_root + .parent() + .ok_or("HOLOLAKE_BROKER_PERSONA_TIME_STORAGE_BOUNDARY_INVALID")? + .join("persona-time-authority-v1"); if let Some(parent) = socket_path.parent() { fs::create_dir_all(parent) .map_err(|error| format!("HOLOLAKE_BROKER_RUNTIME_DIR_FAILED: {error}"))?; @@ -452,6 +462,7 @@ fn start_at( pncc_remote: pncc_remote_root, pncc_projection: pncc_projection_root, language_inbox: language_inbox_root, + persona_time: persona_time_root, }, &worker_shutdown, &worker_authenticated_connections, @@ -530,15 +541,7 @@ fn serve_connection( let response = if read as u64 > MAX_REQUEST_BYTES || !bytes.ends_with(b"\n") { BrokerResponse::error("HOLOLAKE_BROKER_REQUEST_TOO_LARGE") } else { - dispatch( - &roots.session, - &roots.routing, - &roots.pncc_mount, - &roots.pncc_remote, - &roots.pncc_projection, - &roots.language_inbox, - &bytes[..bytes.len() - 1], - ) + dispatch(roots, &bytes[..bytes.len() - 1]) }; if response.ok && request_authenticates && authenticated_connection.is_none() { authenticated_connections.fetch_add(1, Ordering::AcqRel); @@ -575,15 +578,14 @@ fn request_establishes_authenticated_connection(bytes: &[u8]) -> bool { }) } -fn dispatch( - session_root: &Path, - routing_root: &Path, - pncc_mount_root: &Path, - pncc_remote_root: &Path, - pncc_projection_root: &Path, - language_inbox_root: &Path, - bytes: &[u8], -) -> BrokerResponse { +fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse { + let session_root = &roots.session; + let routing_root = &roots.routing; + let pncc_mount_root = &roots.pncc_mount; + let pncc_remote_root = &roots.pncc_remote; + let pncc_projection_root = &roots.pncc_projection; + let language_inbox_root = &roots.language_inbox; + let persona_time_root = &roots.persona_time; let request: BrokerRequest = match serde_json::from_slice(bytes) { Ok(request) => request, Err(error) => { @@ -668,6 +670,18 @@ fn dispatch( serde_json::to_value(receipt).map_err(|error| error.to_string()) }) } + BrokerRequest::GetBeijingTime => std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|duration| duration.as_millis() as u64) + .map_err(|error| format!("HOLOLAKE_SYSTEM_CLOCK_INVALID: {error}")) + .and_then(beijing_time_coordinate) + .and_then(|coordinate| { + serde_json::to_value(coordinate).map_err(|error| error.to_string()) + }), + BrokerRequest::IssuePersonaTimeTicket(input) => { + issue_authenticated_at(persona_time_root, session_root, input) + .and_then(|ticket| serde_json::to_value(ticket).map_err(|error| error.to_string())) + } }; match result { Ok(value) => BrokerResponse::success(value), @@ -840,6 +854,89 @@ mod tests { assert_eq!(resumed["result"]["state"], "RESUMED"); } + #[test] + fn broker_projects_beijing_time_and_issues_a_ticket_only_to_an_authenticated_session() { + 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("accounts-v1/test-account/direct-local-session-v1"); + 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 clock = request( + &socket, + serde_json::json!({ "operation": "GET_BEIJING_TIME" }), + ); + assert_eq!(clock["ok"], true); + assert_eq!(clock["result"]["timeZone"], "Asia/Shanghai (UTC+08:00)"); + assert_eq!(clock["result"]["guanghuEpochDate"], "2025-04-26"); + + let unauthenticated = request( + &socket, + serde_json::json!({ + "operation": "ISSUE_PERSONA_TIME_TICKET", + "input": { + "session": { + "accountId": "human-1", + "sessionId": "invented-session", + "resumeSecret": "invented-secret-long-enough" + }, + "requestId": "time-request-unauthenticated", + "personaId": "ICE-P-ZY001", + "hostSoftwareId": "HOST-CODEX-MACOS-001" + } + }), + ); + assert_eq!(unauthenticated["ok"], false); + + let discovery = issue_ticket_at( + &sessions, + IssueDiscoveryTicketInput { + account_id: "human-1".into(), + lane_id: "codex-current-channel".into(), + client_instance_id: "codex-instance-1".into(), + }, + ) + .unwrap(); + let opened = request( + &socket, + serde_json::json!({ + "operation": "OPEN_SESSION", + "input": { + "accountId": "human-1", + "laneId": "codex-current-channel", + "clientInstanceId": "codex-instance-1", + "discoveryTicket": discovery.discovery_ticket + } + }), + ); + let issued = request( + &socket, + serde_json::json!({ + "operation": "ISSUE_PERSONA_TIME_TICKET", + "input": { + "session": { + "accountId": "human-1", + "sessionId": opened["result"]["sessionId"], + "resumeSecret": opened["result"]["resumeSecret"] + }, + "requestId": "time-request-1", + "personaId": "ICE-P-ZY001", + "hostSoftwareId": "HOST-CODEX-MACOS-001" + } + }), + ); + assert_eq!(issued["ok"], true); + assert_eq!(issued["result"]["channelId"], "codex-current-channel"); + assert_eq!(issued["result"]["clientInstanceId"], "codex-instance-1"); + assert_eq!(issued["result"]["humanControllerId"], "human-1"); + assert_eq!(issued["result"]["guanghuEpochDate"], "2025-04-26"); + } + #[test] fn broker_can_restart_and_resume_persisted_session() { let temp = TempDir::new().unwrap(); diff --git a/product-source/hololake-native-desktop/src-tauri/src/direct_local_session.rs b/product-source/hololake-native-desktop/src-tauri/src/direct_local_session.rs index dc26156fd..b4a8e175b 100644 --- a/product-source/hololake-native-desktop/src-tauri/src/direct_local_session.rs +++ b/product-source/hololake-native-desktop/src-tauri/src/direct_local_session.rs @@ -70,6 +70,14 @@ pub struct AuthenticateSessionInput { pub resume_secret: String, } +#[derive(Clone, Debug)] +pub(crate) struct AuthenticatedSessionContext { + pub account_key: String, + pub session_id: String, + pub lane_id: String, + pub client_instance_id: String, +} + #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct DirectSessionReceipt { @@ -459,6 +467,35 @@ pub(crate) fn authenticate_with_lane_at( Ok(record.lane_id) } +pub(crate) fn authenticate_context_at( + root: &Path, + input: &AuthenticateSessionInput, +) -> Result { + validate_identifier(&input.account_id, "ACCOUNT")?; + validate_identifier(&input.session_id, "SESSION")?; + validate_secret(&input.resume_secret, "RESUME_SECRET")?; + let account_key = sha256_hex(input.account_id.as_bytes()); + require_active_session(root, &account_key, &input.session_id)?; + let path = session_path(root, &account_key, &input.session_id); + let _lock = lock_session(&path)?; + let record = read_session(&path)?; + authorize( + &record, + &account_key, + &input.session_id, + &input.resume_secret, + )?; + if record.lane_id == "visitor-expression-only" { + return Err("HOLOLAKE_VISITOR_SESSION_HAS_NO_SYSTEM_AUTHORITY".into()); + } + Ok(AuthenticatedSessionContext { + account_key, + session_id: record.session_id, + lane_id: record.lane_id, + client_instance_id: record.client_instance_id, + }) +} + pub(crate) fn authenticate_privileged_at( root: &Path, input: &AuthenticateSessionInput, diff --git a/product-source/hololake-native-desktop/src-tauri/src/lib.rs b/product-source/hololake-native-desktop/src-tauri/src/lib.rs index 498ff8b3b..4d39bef3b 100644 --- a/product-source/hololake-native-desktop/src-tauri/src/lib.rs +++ b/product-source/hololake-native-desktop/src-tauri/src/lib.rs @@ -14,6 +14,7 @@ mod glp_envelope; mod home_status; mod knowledge_base; mod local_development_bridge; +mod persona_time_authority; mod personal_channel; mod pncc_receipt_projection; mod pncc_remote_git; @@ -53,6 +54,8 @@ pub fn run() { personal_channel::initialize_personal_channel, personal_channel::create_personal_channel_task, personal_channel::transition_personal_channel_task, + persona_time_authority::issue_persona_time_ticket, + persona_time_authority::get_beijing_time_coordinate, knowledge_base::get_knowledge_snapshot, knowledge_base::read_knowledge_document, knowledge_base::search_knowledge, diff --git a/product-source/hololake-native-desktop/src-tauri/src/persona_time_authority.rs b/product-source/hololake-native-desktop/src-tauri/src/persona_time_authority.rs new file mode 100644 index 000000000..b7a4b018d --- /dev/null +++ b/product-source/hololake-native-desktop/src-tauri/src/persona_time_authority.rs @@ -0,0 +1,546 @@ +//! 光湖人格时间主控:为已认证的同设备会话签发持久、唯一、可重放核验的事件坐标票。 +//! +//! 物理时钟只是一项观测。正式唯一时间由本地权威编号、单调签发序列和 +//! 同毫秒逻辑序列共同组成;系统时钟回拨不会让已签发时间倒退。 + +use ring::digest::{digest, SHA256}; +use rusqlite::{params, Connection, OptionalExtension, TransactionBehavior}; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::time::{SystemTime, UNIX_EPOCH}; +use tauri::AppHandle; +use uuid::Uuid; + +use crate::direct_local_session::{ + authenticate_context_at, direct_session_root, AuthenticateSessionInput, + AuthenticatedSessionContext, +}; + +const TICKET_SCHEMA: &str = "hololake.persona-time-ticket/v1"; +const MAX_ID_BYTES: usize = 160; +const GUANGHU_EPOCH_DATE: &str = "2025-04-26"; +const GUANGHU_EPOCH_BEIJING_DAY_INDEX: i64 = 20_204; +const MILLISECOND_PRECISION_TRANSITION_DATE: &str = "2026-08-17"; + +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct IssuePersonaTimeTicketInput { + pub session: AuthenticateSessionInput, + pub request_id: String, + pub persona_id: String, + pub host_software_id: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct PersonaTimeTicket { + pub schema: String, + pub state: String, + pub authority_id: String, + pub ticket_id: String, + pub unique_timestamp: String, + pub physical_unix_ms: u64, + pub beijing_time: String, + pub time_zone: String, + pub clock_source: String, + pub clock_verification: String, + pub guanghu_epoch_date: String, + pub guanghu_calendar_state: String, + pub elapsed_beijing_dates_since_guanghu_epoch: i64, + pub guanghu_era_day: i64, + pub historical_epoch_precision: String, + pub millisecond_precision_transition_date: String, + pub millisecond_chain_origin_ticket_id: String, + pub logical_counter: u64, + pub issuance_sequence: u64, + pub clock_rollback_observed: bool, + pub human_controller_id: String, + pub human_controller_verification: String, + pub persona_id: String, + pub persona_verification: String, + pub host_software_id: String, + pub host_software_verification: String, + pub client_instance_id: String, + pub channel_id: String, + pub session_id: String, + pub previous_ticket_id: Option, + pub request_id: String, + pub receipt_sha256: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct BeijingTimeCoordinate { + pub schema: String, + pub state: String, + pub unix_ms: u64, + pub beijing_time: String, + pub time_zone: String, + pub clock_source: String, + pub clock_verification: String, + pub continues_while_hololake_is_closed: bool, + pub guanghu_epoch_date: String, + pub guanghu_calendar_state: String, + pub elapsed_beijing_dates_since_guanghu_epoch: i64, + pub guanghu_era_day: i64, + pub historical_epoch_precision: String, + pub millisecond_precision_transition_date: String, + pub millisecond_chain_state: String, +} + +#[derive(Clone, Debug)] +pub(crate) struct VerifiedTicketRequest { + pub request_id: String, + pub human_controller_id: String, + pub persona_id: String, + pub host_software_id: String, + pub session: AuthenticatedSessionContext, +} + +#[tauri::command] +pub async fn issue_persona_time_ticket( + app: AppHandle, + input: IssuePersonaTimeTicketInput, +) -> Result { + let session_root = direct_session_root(&app)?; + let authority_root = authority_root(&app)?; + tauri::async_runtime::spawn_blocking(move || { + issue_authenticated_at(&authority_root, &session_root, input) + }) + .await + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_JOIN_FAILED: {error}"))? +} + +pub(crate) fn issue_authenticated_at( + authority_root: &Path, + session_root: &Path, + input: IssuePersonaTimeTicketInput, +) -> Result { + let session = authenticate_context_at(session_root, &input.session)?; + issue_at( + authority_root, + VerifiedTicketRequest { + request_id: input.request_id, + human_controller_id: input.session.account_id, + persona_id: input.persona_id, + host_software_id: input.host_software_id, + session, + }, + now_unix_ms()?, + ) +} + +#[tauri::command] +pub fn get_beijing_time_coordinate() -> Result { + beijing_time_coordinate(now_unix_ms()?) +} + +pub(crate) fn beijing_time_coordinate(unix_ms: u64) -> Result { + let elapsed_dates = beijing_day_index(unix_ms) - GUANGHU_EPOCH_BEIJING_DAY_INDEX; + Ok(BeijingTimeCoordinate { + schema: "hololake.beijing-time-coordinate/v1".into(), + state: "FLOWING_REALITY_TIME".into(), + unix_ms, + beijing_time: format_beijing_time(unix_ms)?, + time_zone: "Asia/Shanghai (UTC+08:00)".into(), + clock_source: "HOST_OPERATING_SYSTEM_REALTIME_CLOCK".into(), + clock_verification: "LOCAL_CLOCK_NOT_NETWORK_ATTESTED".into(), + continues_while_hololake_is_closed: true, + guanghu_epoch_date: GUANGHU_EPOCH_DATE.into(), + guanghu_calendar_state: "EPOCH_DATE_LOCKED_EXACT_INSTANT_PENDING".into(), + elapsed_beijing_dates_since_guanghu_epoch: elapsed_dates, + guanghu_era_day: elapsed_dates + 1, + historical_epoch_precision: "DAY_ONLY_EXACT_TIME_UNKNOWN".into(), + millisecond_precision_transition_date: MILLISECOND_PRECISION_TRANSITION_DATE.into(), + millisecond_chain_state: "BEGINS_AT_FIRST_DURABLE_TIME_TICKET".into(), + }) +} + +pub(crate) fn authority_root(app: &AppHandle) -> Result { + crate::authenticated_storage::account_storage_root(app, "persona-time-authority-v1") +} + +pub(crate) fn issue_at( + root: &Path, + request: VerifiedTicketRequest, + observed_unix_ms: u64, +) -> Result { + validate_request(&request)?; + fs::create_dir_all(root) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_STORAGE_UNAVAILABLE: {error}"))?; + let database_path = root.join("authority.sqlite3"); + let mut connection = Connection::open(database_path) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_STORAGE_UNAVAILABLE: {error}"))?; + connection + .busy_timeout(std::time::Duration::from_secs(5)) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_STORAGE_UNAVAILABLE: {error}"))?; + initialize(&connection)?; + let transaction = connection + .transaction_with_behavior(TransactionBehavior::Immediate) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_LOCK_FAILED: {error}"))?; + + let request_digest = request_digest(&request); + if let Some((stored_digest, receipt_json)) = transaction + .query_row( + "SELECT request_digest, receipt_json FROM tickets WHERE request_id = ?1", + params![request.request_id], + |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)), + ) + .optional() + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_READ_FAILED: {error}"))? + { + if stored_digest != request_digest { + return Err("HOLOLAKE_PERSONA_TIME_REQUEST_ID_CONFLICT".into()); + } + return serde_json::from_str(&receipt_json) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_RECEIPT_INVALID: {error}")); + } + + let authority_id = transaction + .query_row( + "SELECT value FROM metadata WHERE key = 'authority_id'", + [], + |row| row.get::<_, String>(0), + ) + .optional() + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_READ_FAILED: {error}"))? + .unwrap_or_else(|| format!("local-time-authority-{}", Uuid::new_v4())); + transaction + .execute( + "INSERT OR IGNORE INTO metadata(key, value) VALUES('authority_id', ?1)", + params![authority_id], + ) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_WRITE_FAILED: {error}"))?; + + let (last_physical_ms, last_logical_counter, last_sequence, previous_ticket_id) = transaction + .query_row( + "SELECT last_physical_ms, last_logical_counter, last_sequence, last_ticket_id FROM authority_state WHERE singleton = 1", + [], + |row| Ok((row.get::<_, u64>(0)?, row.get::<_, u64>(1)?, row.get::<_, u64>(2)?, row.get::<_, Option>(3)?)), + ) + .optional() + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_READ_FAILED: {error}"))? + .unwrap_or((0, 0, 0, None)); + + let clock_rollback_observed = observed_unix_ms < last_physical_ms; + let physical_unix_ms = observed_unix_ms.max(last_physical_ms); + let logical_counter = if physical_unix_ms > last_physical_ms { + 0 + } else { + last_logical_counter + .checked_add(1) + .ok_or("HOLOLAKE_PERSONA_TIME_LOGICAL_COUNTER_EXHAUSTED")? + }; + let issuance_sequence = last_sequence + .checked_add(1) + .ok_or("HOLOLAKE_PERSONA_TIME_SEQUENCE_EXHAUSTED")?; + let logical_timestamp = + format!("{physical_unix_ms:013}-{logical_counter:010}-{issuance_sequence:020}"); + let unique_timestamp = format!("{authority_id}@{logical_timestamp}"); + let ticket_id = unique_timestamp.clone(); + let beijing_time = format_beijing_time(physical_unix_ms)?; + let elapsed_dates = beijing_day_index(physical_unix_ms) - GUANGHU_EPOCH_BEIJING_DAY_INDEX; + let millisecond_chain_origin_ticket_id = transaction + .query_row( + "SELECT value FROM metadata WHERE key = 'millisecond_chain_origin_ticket_id'", + [], + |row| row.get::<_, String>(0), + ) + .optional() + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_READ_FAILED: {error}"))? + .unwrap_or_else(|| ticket_id.clone()); + transaction + .execute( + "INSERT OR IGNORE INTO metadata(key, value) VALUES('millisecond_chain_origin_ticket_id', ?1)", + params![millisecond_chain_origin_ticket_id], + ) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_WRITE_FAILED: {error}"))?; + let mut ticket = PersonaTimeTicket { + schema: TICKET_SCHEMA.into(), + state: "ISSUED_DURABLE_LOCAL".into(), + authority_id, + ticket_id: ticket_id.clone(), + unique_timestamp, + physical_unix_ms, + beijing_time, + time_zone: "Asia/Shanghai (UTC+08:00)".into(), + clock_source: "HOST_OPERATING_SYSTEM_REALTIME_CLOCK".into(), + clock_verification: "LOCAL_CLOCK_NOT_NETWORK_ATTESTED".into(), + guanghu_epoch_date: GUANGHU_EPOCH_DATE.into(), + guanghu_calendar_state: "EPOCH_DATE_LOCKED_EXACT_INSTANT_PENDING".into(), + elapsed_beijing_dates_since_guanghu_epoch: elapsed_dates, + guanghu_era_day: elapsed_dates + 1, + historical_epoch_precision: "DAY_ONLY_EXACT_TIME_UNKNOWN".into(), + millisecond_precision_transition_date: MILLISECOND_PRECISION_TRANSITION_DATE.into(), + millisecond_chain_origin_ticket_id, + logical_counter, + issuance_sequence, + clock_rollback_observed, + human_controller_id: request.human_controller_id, + human_controller_verification: "AUTHENTICATED_DIRECT_SESSION".into(), + persona_id: request.persona_id, + persona_verification: "UNVERIFIED_CALLER_CLAIM".into(), + host_software_id: request.host_software_id, + host_software_verification: "UNVERIFIED_CALLER_CLAIM".into(), + client_instance_id: request.session.client_instance_id, + channel_id: request.session.lane_id, + session_id: request.session.session_id, + previous_ticket_id, + request_id: request.request_id, + receipt_sha256: String::new(), + }; + ticket.receipt_sha256 = ticket_digest(&ticket); + let receipt_json = serde_json::to_string(&ticket) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_RECEIPT_INVALID: {error}"))?; + + transaction + .execute( + "INSERT INTO tickets(request_id, request_digest, ticket_id, receipt_json) VALUES(?1, ?2, ?3, ?4)", + params![ticket.request_id, request_digest, ticket.ticket_id, receipt_json], + ) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_WRITE_FAILED: {error}"))?; + transaction + .execute( + "INSERT INTO authority_state(singleton, last_physical_ms, last_logical_counter, last_sequence, last_ticket_id) VALUES(1, ?1, ?2, ?3, ?4) + ON CONFLICT(singleton) DO UPDATE SET last_physical_ms=excluded.last_physical_ms, last_logical_counter=excluded.last_logical_counter, last_sequence=excluded.last_sequence, last_ticket_id=excluded.last_ticket_id", + params![physical_unix_ms, logical_counter, issuance_sequence, ticket_id], + ) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_WRITE_FAILED: {error}"))?; + transaction + .commit() + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_COMMIT_FAILED: {error}"))?; + Ok(ticket) +} + +fn initialize(connection: &Connection) -> Result<(), String> { + connection + .execute_batch( + "PRAGMA synchronous=FULL; + CREATE TABLE IF NOT EXISTS metadata(key TEXT PRIMARY KEY, value TEXT NOT NULL); + CREATE TABLE IF NOT EXISTS authority_state( + singleton INTEGER PRIMARY KEY CHECK(singleton = 1), + last_physical_ms INTEGER NOT NULL, + last_logical_counter INTEGER NOT NULL, + last_sequence INTEGER NOT NULL, + last_ticket_id TEXT + ); + CREATE TABLE IF NOT EXISTS tickets( + request_id TEXT PRIMARY KEY, + request_digest TEXT NOT NULL, + ticket_id TEXT NOT NULL UNIQUE, + receipt_json TEXT NOT NULL + );", + ) + .map_err(|error| format!("HOLOLAKE_PERSONA_TIME_STORAGE_UNAVAILABLE: {error}")) +} + +fn validate_request(request: &VerifiedTicketRequest) -> Result<(), String> { + for (kind, value) in [ + ("REQUEST", request.request_id.as_str()), + ("HUMAN_CONTROLLER", request.human_controller_id.as_str()), + ("PERSONA", request.persona_id.as_str()), + ("HOST_SOFTWARE", request.host_software_id.as_str()), + ("SESSION", request.session.session_id.as_str()), + ("CHANNEL", request.session.lane_id.as_str()), + ( + "CLIENT_INSTANCE", + request.session.client_instance_id.as_str(), + ), + ] { + if value.is_empty() || value.len() > MAX_ID_BYTES || value.chars().any(char::is_control) { + return Err(format!("HOLOLAKE_PERSONA_TIME_{kind}_ID_INVALID")); + } + } + if request.session.account_key.is_empty() { + return Err("HOLOLAKE_PERSONA_TIME_SESSION_CONTEXT_INVALID".into()); + } + Ok(()) +} + +fn request_digest(request: &VerifiedTicketRequest) -> String { + sha256_hex( + format!( + "{}\n{}\n{}\n{}\n{}\n{}\n{}", + request.request_id, + request.human_controller_id, + request.persona_id, + request.host_software_id, + request.session.account_key, + request.session.session_id, + request.session.client_instance_id, + ) + .as_bytes(), + ) +} + +fn ticket_digest(ticket: &PersonaTimeTicket) -> String { + sha256_hex( + format!( + "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}", + ticket.authority_id, + ticket.ticket_id, + ticket.unique_timestamp, + ticket.human_controller_id, + ticket.persona_id, + ticket.host_software_id, + ticket.client_instance_id, + ticket.channel_id, + ticket.previous_ticket_id.as_deref().unwrap_or(""), + ) + .as_bytes(), + ) +} + +fn sha256_hex(bytes: &[u8]) -> String { + digest(&SHA256, bytes) + .as_ref() + .iter() + .map(|byte| format!("{byte:02x}")) + .collect() +} + +fn now_unix_ms() -> Result { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_millis() as u64) + .map_err(|error| format!("HOLOLAKE_SYSTEM_CLOCK_INVALID: {error}")) +} + +fn format_beijing_time(unix_ms: u64) -> Result { + const BEIJING_OFFSET_SECONDS: u64 = 8 * 60 * 60; + let unix_seconds = unix_ms / 1000; + let shifted = unix_seconds + .checked_add(BEIJING_OFFSET_SECONDS) + .ok_or("HOLOLAKE_BEIJING_TIME_OUT_OF_RANGE")?; + let days = (shifted / 86_400) as i64; + let seconds_in_day = shifted % 86_400; + let (year, month, day) = civil_from_days(days); + let hour = seconds_in_day / 3600; + let minute = (seconds_in_day % 3600) / 60; + let second = seconds_in_day % 60; + let millisecond = unix_ms % 1000; + Ok(format!( + "{year:04}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}.{millisecond:03}+08:00" + )) +} + +fn beijing_day_index(unix_ms: u64) -> i64 { + const BEIJING_OFFSET_MS: u64 = 8 * 60 * 60 * 1000; + unix_ms + .saturating_add(BEIJING_OFFSET_MS) + .div_euclid(86_400_000) as i64 +} + +// Gregorian civil date conversion from Unix epoch days; valid for the positive +// Unix timestamps accepted by this runtime and independent of process lifetime. +fn civil_from_days(days_since_unix_epoch: i64) -> (i64, u64, u64) { + let z = days_since_unix_epoch + 719_468; + let era = z.div_euclid(146_097); + let day_of_era = z - era * 146_097; + let year_of_era = + (day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365; + let mut year = year_of_era + era * 400; + let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100); + let month_prime = (5 * day_of_year + 2) / 153; + let day = day_of_year - (153 * month_prime + 2) / 5 + 1; + let month = month_prime + if month_prime < 10 { 3 } else { -9 }; + year += i64::from(month <= 2); + (year, month as u64, day as u64) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn request(id: &str, instance: &str) -> VerifiedTicketRequest { + VerifiedTicketRequest { + request_id: id.into(), + human_controller_id: "ICE-GL∞".into(), + persona_id: "ICE-P-ZY001".into(), + host_software_id: "HOST-CODEX-MACOS-001".into(), + session: AuthenticatedSessionContext { + account_key: "verified-account-key".into(), + session_id: "channel-session-1".into(), + lane_id: "codex-current-channel".into(), + client_instance_id: instance.into(), + }, + } + } + + #[test] + fn concurrent_same_millisecond_requests_receive_different_unique_times() { + let temp = tempfile::tempdir().unwrap(); + let mut handles = Vec::new(); + for index in 0..12 { + let root = temp.path().to_path_buf(); + handles.push(std::thread::spawn(move || { + issue_at( + &root, + request(&format!("request-{index}"), &format!("codex-{index}")), + 1000, + ) + .unwrap() + })); + } + let mut tickets = handles + .into_iter() + .map(|handle| handle.join().unwrap()) + .collect::>(); + tickets.sort_by_key(|ticket| ticket.issuance_sequence); + tickets.dedup_by(|left, right| left.unique_timestamp == right.unique_timestamp); + assert_eq!(tickets.len(), 12); + assert_eq!(tickets.last().unwrap().issuance_sequence, 12); + } + + #[test] + fn retry_is_idempotent_but_request_id_reuse_with_other_context_is_rejected() { + let temp = tempfile::tempdir().unwrap(); + let first = issue_at(temp.path(), request("same-request", "codex-1"), 1000).unwrap(); + let retry = issue_at(temp.path(), request("same-request", "codex-1"), 2000).unwrap(); + assert_eq!(first, retry); + let error = issue_at(temp.path(), request("same-request", "qoder-1"), 3000).unwrap_err(); + assert_eq!(error, "HOLOLAKE_PERSONA_TIME_REQUEST_ID_CONFLICT"); + } + + #[test] + fn restart_and_clock_rollback_preserve_monotonic_time_and_chain() { + let temp = tempfile::tempdir().unwrap(); + let first = issue_at(temp.path(), request("request-1", "codex-1"), 5000).unwrap(); + let second = issue_at(temp.path(), request("request-2", "codex-2"), 4000).unwrap(); + assert!(second.clock_rollback_observed); + assert_eq!(second.physical_unix_ms, first.physical_unix_ms); + assert!(second.unique_timestamp > first.unique_timestamp); + assert_eq!( + second.previous_ticket_id.as_deref(), + Some(first.ticket_id.as_str()) + ); + assert_eq!(second.authority_id, first.authority_id); + } + + #[test] + fn reality_time_is_projected_as_beijing_time_even_without_process_uptime() { + let coordinate = beijing_time_coordinate(0).unwrap(); + assert_eq!(coordinate.beijing_time, "1970-01-01T08:00:00.000+08:00"); + assert!(coordinate.continues_while_hololake_is_closed); + assert_eq!( + coordinate.clock_verification, + "LOCAL_CLOCK_NOT_NETWORK_ATTESTED" + ); + + let known = beijing_time_coordinate(1_786_947_831_456).unwrap(); + assert_eq!(known.beijing_time, "2026-08-17T14:23:51.456+08:00"); + assert_eq!( + beijing_time_coordinate(1_745_596_800_000) + .unwrap() + .elapsed_beijing_dates_since_guanghu_epoch, + 0 + ); + assert_eq!( + beijing_time_coordinate(1_745_596_800_000) + .unwrap() + .guanghu_era_day, + 1 + ); + } +}