feat: add HoloLake persona time authority
This commit is contained in:
parent
a2fb2760f8
commit
a96c5955bb
7 changed files with 827 additions and 18 deletions
|
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"TCS_LANGUAGE_CONTRACT",
|
"TCS_LANGUAGE_CONTRACT",
|
||||||
"HOST_CAPABILITY_RECEIPTS",
|
"HOST_CAPABILITY_RECEIPTS",
|
||||||
"EVENT_AND_LAKE_LAMP",
|
"EVENT_AND_LAKE_LAMP",
|
||||||
|
"PERSONA_TIME_AUTHORITY",
|
||||||
"MEMORY_GIT_EVIDENCE",
|
"MEMORY_GIT_EVIDENCE",
|
||||||
"KNOWLEDGE_PROJECTION",
|
"KNOWLEDGE_PROJECTION",
|
||||||
"HUMAN_APPROVAL_CENTER",
|
"HUMAN_APPROVAL_CENTER",
|
||||||
|
|
@ -112,6 +113,16 @@
|
||||||
"public_developer_id_and_notarization": false,
|
"public_developer_id_and_notarization": false,
|
||||||
"server_deployment": 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"],
|
"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"],
|
"context_risk_signals": ["MODEL_CONTEXT_SPEC", "ESTIMATED_TOKENS", "MESSAGE_VOLUME", "TURN_COUNT", "TASK_STAGE", "HOST_WARNING"],
|
||||||
"exact_host_compaction_prediction_claimed": false,
|
"exact_host_compaction_prediction_claimed": false,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
})
|
||||||
|
|
@ -9,6 +9,9 @@ use crate::dynamic_capability_routing::{
|
||||||
routing_root as dynamic_routing_root, DynamicNodeRegistry, ResolveCapabilityRouteInput,
|
routing_root as dynamic_routing_root, DynamicNodeRegistry, ResolveCapabilityRouteInput,
|
||||||
SignedNodeHealth,
|
SignedNodeHealth,
|
||||||
};
|
};
|
||||||
|
use crate::persona_time_authority::{
|
||||||
|
beijing_time_coordinate, issue_authenticated_at, IssuePersonaTimeTicketInput,
|
||||||
|
};
|
||||||
use crate::pncc_receipt_projection::{
|
use crate::pncc_receipt_projection::{
|
||||||
append_remote_read_at as project_pncc_remote_read_at,
|
append_remote_read_at as project_pncc_remote_read_at,
|
||||||
append_repository_binding_at as project_pncc_repository_binding_at,
|
append_repository_binding_at as project_pncc_repository_binding_at,
|
||||||
|
|
@ -115,6 +118,7 @@ struct BrokerStorageRoots {
|
||||||
pncc_remote: PathBuf,
|
pncc_remote: PathBuf,
|
||||||
pncc_projection: PathBuf,
|
pncc_projection: PathBuf,
|
||||||
language_inbox: PathBuf,
|
language_inbox: PathBuf,
|
||||||
|
persona_time: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for DirectLocalBrokerHandle {
|
impl Drop for DirectLocalBrokerHandle {
|
||||||
|
|
@ -148,6 +152,8 @@ enum BrokerRequest {
|
||||||
InspectMountedPnccRepository(AuthenticatedPnccMountInput),
|
InspectMountedPnccRepository(AuthenticatedPnccMountInput),
|
||||||
ReadMountedPnccRemoteObject(AuthenticatedPnccRemoteReadInput),
|
ReadMountedPnccRemoteObject(AuthenticatedPnccRemoteReadInput),
|
||||||
QueryPnccReceiptProjection(AuthenticatedPnccProjectionQueryInput),
|
QueryPnccReceiptProjection(AuthenticatedPnccProjectionQueryInput),
|
||||||
|
GetBeijingTime,
|
||||||
|
IssuePersonaTimeTicket(IssuePersonaTimeTicketInput),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
|
@ -398,6 +404,10 @@ fn start_at(
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or("HOLOLAKE_BROKER_LANGUAGE_STORAGE_BOUNDARY_INVALID")?
|
.ok_or("HOLOLAKE_BROKER_LANGUAGE_STORAGE_BOUNDARY_INVALID")?
|
||||||
.join("circular-lake-language-inbox-v1");
|
.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() {
|
if let Some(parent) = socket_path.parent() {
|
||||||
fs::create_dir_all(parent)
|
fs::create_dir_all(parent)
|
||||||
.map_err(|error| format!("HOLOLAKE_BROKER_RUNTIME_DIR_FAILED: {error}"))?;
|
.map_err(|error| format!("HOLOLAKE_BROKER_RUNTIME_DIR_FAILED: {error}"))?;
|
||||||
|
|
@ -452,6 +462,7 @@ fn start_at(
|
||||||
pncc_remote: pncc_remote_root,
|
pncc_remote: pncc_remote_root,
|
||||||
pncc_projection: pncc_projection_root,
|
pncc_projection: pncc_projection_root,
|
||||||
language_inbox: language_inbox_root,
|
language_inbox: language_inbox_root,
|
||||||
|
persona_time: persona_time_root,
|
||||||
},
|
},
|
||||||
&worker_shutdown,
|
&worker_shutdown,
|
||||||
&worker_authenticated_connections,
|
&worker_authenticated_connections,
|
||||||
|
|
@ -530,15 +541,7 @@ fn serve_connection(
|
||||||
let response = if read as u64 > MAX_REQUEST_BYTES || !bytes.ends_with(b"\n") {
|
let response = if read as u64 > MAX_REQUEST_BYTES || !bytes.ends_with(b"\n") {
|
||||||
BrokerResponse::error("HOLOLAKE_BROKER_REQUEST_TOO_LARGE")
|
BrokerResponse::error("HOLOLAKE_BROKER_REQUEST_TOO_LARGE")
|
||||||
} else {
|
} else {
|
||||||
dispatch(
|
dispatch(roots, &bytes[..bytes.len() - 1])
|
||||||
&roots.session,
|
|
||||||
&roots.routing,
|
|
||||||
&roots.pncc_mount,
|
|
||||||
&roots.pncc_remote,
|
|
||||||
&roots.pncc_projection,
|
|
||||||
&roots.language_inbox,
|
|
||||||
&bytes[..bytes.len() - 1],
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
if response.ok && request_authenticates && authenticated_connection.is_none() {
|
if response.ok && request_authenticates && authenticated_connection.is_none() {
|
||||||
authenticated_connections.fetch_add(1, Ordering::AcqRel);
|
authenticated_connections.fetch_add(1, Ordering::AcqRel);
|
||||||
|
|
@ -575,15 +578,14 @@ fn request_establishes_authenticated_connection(bytes: &[u8]) -> bool {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch(
|
fn dispatch(roots: &BrokerStorageRoots, bytes: &[u8]) -> BrokerResponse {
|
||||||
session_root: &Path,
|
let session_root = &roots.session;
|
||||||
routing_root: &Path,
|
let routing_root = &roots.routing;
|
||||||
pncc_mount_root: &Path,
|
let pncc_mount_root = &roots.pncc_mount;
|
||||||
pncc_remote_root: &Path,
|
let pncc_remote_root = &roots.pncc_remote;
|
||||||
pncc_projection_root: &Path,
|
let pncc_projection_root = &roots.pncc_projection;
|
||||||
language_inbox_root: &Path,
|
let language_inbox_root = &roots.language_inbox;
|
||||||
bytes: &[u8],
|
let persona_time_root = &roots.persona_time;
|
||||||
) -> BrokerResponse {
|
|
||||||
let request: BrokerRequest = match serde_json::from_slice(bytes) {
|
let request: BrokerRequest = match serde_json::from_slice(bytes) {
|
||||||
Ok(request) => request,
|
Ok(request) => request,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
|
@ -668,6 +670,18 @@ fn dispatch(
|
||||||
serde_json::to_value(receipt).map_err(|error| error.to_string())
|
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 {
|
match result {
|
||||||
Ok(value) => BrokerResponse::success(value),
|
Ok(value) => BrokerResponse::success(value),
|
||||||
|
|
@ -840,6 +854,89 @@ mod tests {
|
||||||
assert_eq!(resumed["result"]["state"], "RESUMED");
|
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]
|
#[test]
|
||||||
fn broker_can_restart_and_resume_persisted_session() {
|
fn broker_can_restart_and_resume_persisted_session() {
|
||||||
let temp = TempDir::new().unwrap();
|
let temp = TempDir::new().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,14 @@ pub struct AuthenticateSessionInput {
|
||||||
pub resume_secret: String,
|
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)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DirectSessionReceipt {
|
pub struct DirectSessionReceipt {
|
||||||
|
|
@ -459,6 +467,35 @@ pub(crate) fn authenticate_with_lane_at(
|
||||||
Ok(record.lane_id)
|
Ok(record.lane_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn authenticate_context_at(
|
||||||
|
root: &Path,
|
||||||
|
input: &AuthenticateSessionInput,
|
||||||
|
) -> Result<AuthenticatedSessionContext, String> {
|
||||||
|
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(
|
pub(crate) fn authenticate_privileged_at(
|
||||||
root: &Path,
|
root: &Path,
|
||||||
input: &AuthenticateSessionInput,
|
input: &AuthenticateSessionInput,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ mod glp_envelope;
|
||||||
mod home_status;
|
mod home_status;
|
||||||
mod knowledge_base;
|
mod knowledge_base;
|
||||||
mod local_development_bridge;
|
mod local_development_bridge;
|
||||||
|
mod persona_time_authority;
|
||||||
mod personal_channel;
|
mod personal_channel;
|
||||||
mod pncc_receipt_projection;
|
mod pncc_receipt_projection;
|
||||||
mod pncc_remote_git;
|
mod pncc_remote_git;
|
||||||
|
|
@ -53,6 +54,8 @@ pub fn run() {
|
||||||
personal_channel::initialize_personal_channel,
|
personal_channel::initialize_personal_channel,
|
||||||
personal_channel::create_personal_channel_task,
|
personal_channel::create_personal_channel_task,
|
||||||
personal_channel::transition_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::get_knowledge_snapshot,
|
||||||
knowledge_base::read_knowledge_document,
|
knowledge_base::read_knowledge_document,
|
||||||
knowledge_base::search_knowledge,
|
knowledge_base::search_knowledge,
|
||||||
|
|
|
||||||
|
|
@ -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<String>,
|
||||||
|
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<PersonaTimeTicket, String> {
|
||||||
|
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<PersonaTimeTicket, String> {
|
||||||
|
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<BeijingTimeCoordinate, String> {
|
||||||
|
beijing_time_coordinate(now_unix_ms()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn beijing_time_coordinate(unix_ms: u64) -> Result<BeijingTimeCoordinate, String> {
|
||||||
|
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<PathBuf, String> {
|
||||||
|
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<PersonaTimeTicket, String> {
|
||||||
|
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<String>>(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<u64, String> {
|
||||||
|
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<String, String> {
|
||||||
|
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::<Vec<_>>();
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue