feat: surface Guanghu era time authority

This commit is contained in:
冰朔 2026-08-17 14:06:45 +08:00
commit 3294447dce
8 changed files with 733 additions and 23 deletions

View file

@ -12,6 +12,7 @@ use uuid::Uuid;
const KERNEL_SCHEMA: &str = "hololake.personal-channel-kernel/v1";
const DATABASE_SCHEMA_VERSION: i64 = 1;
const ZERO_HASH: &str = "0000000000000000000000000000000000000000000000000000000000000000";
const TIME_AUTHORITY_MODULE_ID: &str = "hololake.persona-time-authority";
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -68,6 +69,19 @@ pub struct PersonalChannelEventProjection {
pub receipt_hash: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonalChannelModule {
pub module_id: String,
pub kind: String,
pub display_name: String,
pub state: String,
pub installed_at_unix_ms: i64,
pub time_zone: String,
pub calendar_name: String,
pub clock_verification: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PersonalChannelIntegrity {
@ -87,6 +101,7 @@ pub struct PersonalChannelSnapshot {
pub identity: Option<PersonalChannelIdentity>,
pub current_task: Option<PersonalChannelTask>,
pub recent_events: Vec<PersonalChannelEventProjection>,
pub modules: Vec<PersonalChannelModule>,
pub integrity: PersonalChannelIntegrity,
pub storage: &'static str,
pub authority: &'static str,
@ -204,6 +219,29 @@ fn open_database(path: &Path) -> Result<Connection, String> {
);
CREATE UNIQUE INDEX IF NOT EXISTS one_active_personal_task
ON tasks(status) WHERE status = 'ACTIVE';
CREATE TABLE IF NOT EXISTS channel_modules (
module_id TEXT PRIMARY KEY NOT NULL,
kind TEXT NOT NULL,
display_name TEXT NOT NULL,
state TEXT NOT NULL,
installed_at_unix_ms INTEGER NOT NULL,
time_zone TEXT NOT NULL,
calendar_name TEXT NOT NULL,
clock_verification TEXT NOT NULL
);
INSERT OR IGNORE INTO channel_modules(
module_id, kind, display_name, state, installed_at_unix_ms,
time_zone, calendar_name, clock_verification
)
SELECT 'hololake.persona-time-authority', 'PERSONA_TIME_AUTHORITY',
'', 'READY_EVENT_TRIGGERED_TIME_AUTHORITY',
created_at_unix_ms, 'Asia/Shanghai (UTC+08:00)', '',
'DYNAMIC_NETWORK_SYNC_OR_EXPLICIT_LOCAL_FALLBACK'
FROM identities WHERE singleton = 1;
UPDATE channel_modules
SET state = 'READY_EVENT_TRIGGERED_TIME_AUTHORITY',
clock_verification = 'DYNAMIC_NETWORK_SYNC_OR_EXPLICIT_LOCAL_FALLBACK'
WHERE module_id = 'hololake.persona-time-authority';
CREATE TABLE IF NOT EXISTS events (
sequence INTEGER PRIMARY KEY NOT NULL,
event_id TEXT NOT NULL UNIQUE,
@ -278,18 +316,38 @@ fn initialize_at(
params![human_subject_id, display_name, channel_id, created_at],
)
.map_err(database_write_error)?;
install_time_authority_module(&transaction, created_at)?;
append_event(
&transaction,
&human_subject_id,
"CHANNEL_INITIALIZED",
None,
&format!("{display_name} 建立了个人频道"),
&format!("{display_name} 建立了个人频道,并预装时间主控"),
created_at,
)?;
transaction.commit().map_err(database_write_error)?;
snapshot_at(database)
}
fn install_time_authority_module(
transaction: &Transaction<'_>,
installed_at_unix_ms: i64,
) -> Result<(), String> {
transaction
.execute(
"INSERT INTO channel_modules(
module_id, kind, display_name, state, installed_at_unix_ms,
time_zone, calendar_name, clock_verification
) VALUES(?1, 'PERSONA_TIME_AUTHORITY', '',
'READY_EVENT_TRIGGERED_TIME_AUTHORITY', ?2,
'Asia/Shanghai (UTC+08:00)', '',
'DYNAMIC_NETWORK_SYNC_OR_EXPLICIT_LOCAL_FALLBACK')",
params![TIME_AUTHORITY_MODULE_ID, installed_at_unix_ms],
)
.map_err(database_write_error)?;
Ok(())
}
fn create_task_at(
database: &Path,
input: CreatePersonalChannelTaskInput,
@ -544,6 +602,29 @@ fn snapshot_at(database: &Path) -> Result<PersonalChannelSnapshot, String> {
.map_err(database_read_error)?
.collect::<Result<Vec<_>, _>>()
.map_err(database_read_error)?;
let mut module_statement = connection
.prepare(
"SELECT module_id, kind, display_name, state, installed_at_unix_ms,
time_zone, calendar_name, clock_verification
FROM channel_modules ORDER BY installed_at_unix_ms, module_id",
)
.map_err(database_read_error)?;
let modules = module_statement
.query_map([], |row| {
Ok(PersonalChannelModule {
module_id: row.get(0)?,
kind: row.get(1)?,
display_name: row.get(2)?,
state: row.get(3)?,
installed_at_unix_ms: row.get(4)?,
time_zone: row.get(5)?,
calendar_name: row.get(6)?,
clock_verification: row.get(7)?,
})
})
.map_err(database_read_error)?
.collect::<Result<Vec<_>, _>>()
.map_err(database_read_error)?;
Ok(PersonalChannelSnapshot {
schema: KERNEL_SCHEMA,
state: if identity.is_some() {
@ -554,6 +635,7 @@ fn snapshot_at(database: &Path) -> Result<PersonalChannelSnapshot, String> {
identity,
current_task,
recent_events,
modules,
integrity,
storage: "LOCAL_PRIVATE_SQLITE_SINGLE_HOLOLAKE_OWNER",
authority: "LOCAL_HUMAN_CONFIRMED_IDENTITY_NOT_SERVER_AUTHORITY",
@ -661,6 +743,16 @@ fn verify_integrity(connection: &Connection) -> Result<PersonalChannelIntegrity,
if identity_count > 1 || (identity_count == 1 && event_count == 0) {
return Err("HOLOLAKE_PERSONAL_CHANNEL_INTEGRITY_FAILED".into());
}
let time_module_count: i64 = connection
.query_row(
"SELECT COUNT(*) FROM channel_modules WHERE module_id = ?1",
params![TIME_AUTHORITY_MODULE_ID],
|row| row.get(0),
)
.map_err(database_read_error)?;
if time_module_count != identity_count {
return Err("HOLOLAKE_PERSONAL_CHANNEL_TIME_MODULE_INTEGRITY_FAILED".into());
}
Ok(PersonalChannelIntegrity {
state: "PASS_100",
schema_version,
@ -748,6 +840,13 @@ mod tests {
assert_eq!(snapshot.integrity.receipt_count, 1);
assert_eq!(snapshot.recent_events[0].kind, "CHANNEL_INITIALIZED");
assert!(snapshot.recent_events[0].receipt_id.starts_with("HLR-"));
assert_eq!(snapshot.modules.len(), 1);
assert_eq!(snapshot.modules[0].module_id, TIME_AUTHORITY_MODULE_ID);
assert_eq!(snapshot.modules[0].display_name, "时间主控");
assert_eq!(
snapshot.modules[0].clock_verification,
"DYNAMIC_NETWORK_SYNC_OR_EXPLICIT_LOCAL_FALLBACK"
);
}
#[test]
@ -774,6 +873,27 @@ mod tests {
assert_eq!(after_restart.integrity.last_receipt_hash, before_hash);
}
#[test]
fn an_existing_channel_receives_the_time_module_idempotently() {
let temp = TempDir::new().unwrap();
let database = database(&temp);
initialize(&database);
let connection = open_database(&database).unwrap();
connection
.execute(
"DELETE FROM channel_modules WHERE module_id = ?1",
params![TIME_AUTHORITY_MODULE_ID],
)
.unwrap();
drop(connection);
let migrated = snapshot_at(&database).unwrap();
assert_eq!(migrated.modules.len(), 1);
assert_eq!(migrated.modules[0].module_id, TIME_AUTHORITY_MODULE_ID);
let reread = snapshot_at(&database).unwrap();
assert_eq!(reread.modules.len(), 1);
}
#[test]
fn one_active_task_is_enforced_and_completion_is_receipted() {
let temp = TempDir::new().unwrap();