feat: surface Guanghu era time authority
This commit is contained in:
parent
a96c5955bb
commit
3294447dce
8 changed files with 733 additions and 23 deletions
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue