asurada 0.3.0

Asurada — a memory + cognition daemon that grows with the user. Local-first, BYOK, shared by Devist/Webchemist Core/etc.
// brain.db: SQLite + FTS5.
// WAL 모드, 동시 읽기 허용. Devist 와 Asurada 가 같은 파일을 공유하므로
// 양쪽 모두 짧은 트랜잭션으로 쓰고 busy_timeout 설정.

use anyhow::{Context, Result};
use rusqlite::Connection;
use std::path::Path;

pub mod advice;
pub mod event;
pub mod intent;
pub mod issue;
pub mod memory;
pub mod pattern;
pub mod project;
pub mod schema;
pub mod sync_state;

pub use memory::MemoryInput;

/// brain.db 를 열고 마이그레이션 적용. WAL 모드 활성화.
pub fn open(path: &Path) -> Result<Connection> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent).ok();
    }
    let conn = Connection::open(path).with_context(|| format!("open {}", path.display()))?;

    // 동시 읽기 + 단일 쓰기, busy 시 5초 대기.
    // Devist 와 Asurada 가 같이 쓸 수 있으므로 busy_timeout 필수.
    conn.pragma_update(None, "journal_mode", "WAL")?;
    conn.pragma_update(None, "synchronous", "NORMAL")?;
    conn.pragma_update(None, "busy_timeout", 5000)?;
    conn.pragma_update(None, "foreign_keys", "ON")?;

    schema::migrate(&conn)?;
    Ok(conn)
}

/// 비-crypto UUID-like 식별자. 시간 + PID + 주소 비트로 충분히 unique.
/// 모든 DB 모듈이 공유하는 ID 생성기.
pub(crate) fn uuid_like() -> String {
    use std::time::{SystemTime, UNIX_EPOCH};
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_nanos())
        .unwrap_or(0);
    let pid = std::process::id();
    let rand = std::ptr::addr_of!(nanos) as usize;
    format!("{:016x}-{:08x}-{:08x}", nanos, pid, rand)
}