mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
@@ -1819,6 +1819,20 @@ async fn run_server_inner(
|
||||
),
|
||||
Err(e) => log::warn!("[boot] whatsapp_data::global init failed: {e}"),
|
||||
}
|
||||
// Seed the people store so people controllers + `people_*`
|
||||
// tools can read/write. Without this the process-global stays
|
||||
// empty and every call fails with "people store not
|
||||
// initialised" (Sentry TAURI-RUST-8NM). Sits inside this
|
||||
// Ok(cfg) arm so it inherits the wrong-workspace guard above
|
||||
// (never seed against a Config::default fallback).
|
||||
match crate::openhuman::people::store::init_from_workspace(&cfg.workspace_dir).await
|
||||
{
|
||||
Ok(_) => log::info!(
|
||||
"[boot] people::store initialized (workspace={})",
|
||||
cfg.workspace_dir.display()
|
||||
),
|
||||
Err(e) => log::warn!("[boot] people::store init failed: {e}"),
|
||||
}
|
||||
// Prune legacy bundled skills (dev-workflow / github-issue-crusher
|
||||
// / pr-review-shepherd) that older builds seeded into
|
||||
// <workspace>/skills/. OpenHuman no longer ships bundled defaults;
|
||||
|
||||
@@ -58,7 +58,7 @@ Dedicated SQLite DB managed by `PeopleStore` (open via `open_at(path)` or `open_
|
||||
- `handle_aliases` — `(kind, value)` primary key → `person_id` (FK, `ON DELETE CASCADE`); `value` is the canonicalized form. This table *is* the resolver index.
|
||||
- `interactions` — `(person_id, ts, is_outbound, length)` rows the scorer aggregates; indexed by `(person_id, ts DESC)` and `ts DESC`.
|
||||
|
||||
Migrations are tracked in `_people_migrations` and applied idempotently in a transaction. The store is exposed process-globally through a `tokio::sync::OnceCell` (`init` once at startup, `get` from controller handlers). Note: no `people::store::init` call was found outside the module — controller handlers will return "people store not initialised" until something seeds it.
|
||||
Migrations are tracked in `_people_migrations` and applied idempotently in a transaction. The store is exposed process-globally through a `tokio::sync::OnceCell` (`get` from controller handlers). Core boot seeds it via `store::init_from_workspace(workspace_dir)` (`src/core/jsonrpc.rs`, alongside `memory::global` and `whatsapp_data::global`), opening `<workspace>/people/people.db`; tests construct stores directly with `open_in_memory`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
||||
@@ -29,6 +29,33 @@ pub async fn init(store: Arc<PeopleStore>) -> Result<(), &'static str> {
|
||||
.map_err(|_| "people store already initialised")
|
||||
}
|
||||
|
||||
/// Seed the process-global store from a workspace directory, opening the
|
||||
/// on-disk db at `<workspace>/people/people.db` (schema migrations run on
|
||||
/// open). Idempotent: a second call — or a concurrent boot path — is a no-op
|
||||
/// that returns the already-seeded store rather than erroring.
|
||||
///
|
||||
/// Wired into core boot (`src/core/jsonrpc.rs`) alongside `memory::global` and
|
||||
/// `whatsapp_data::global`. Without this seed the global stays empty and every
|
||||
/// people controller / `people_*` tool fails with "people store not
|
||||
/// initialised" (Sentry TAURI-RUST-8NM).
|
||||
pub async fn init_from_workspace(
|
||||
workspace_dir: &std::path::Path,
|
||||
) -> Result<Arc<PeopleStore>, String> {
|
||||
if let Some(existing) = GLOBAL.get() {
|
||||
log::debug!("[people:store] already initialised");
|
||||
return Ok(existing.clone());
|
||||
}
|
||||
let db_path = workspace_dir.join("people").join("people.db");
|
||||
let store = Arc::new(
|
||||
PeopleStore::open_at(&db_path).map_err(|e| format!("people store open failed: {e}"))?,
|
||||
);
|
||||
// Race-resolve: another caller may have seeded while we were opening.
|
||||
match GLOBAL.set(store.clone()) {
|
||||
Ok(()) => Ok(store),
|
||||
Err(_) => Ok(GLOBAL.get().cloned().unwrap_or(store)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get() -> Result<Arc<PeopleStore>, &'static str> {
|
||||
GLOBAL
|
||||
.get()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Cross-file integration tests for the people domain.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
use crate::openhuman::people::address_book;
|
||||
@@ -57,6 +59,31 @@ fn schema_exposes_four_controllers() {
|
||||
assert_eq!(names.len(), 4);
|
||||
}
|
||||
|
||||
/// Regression for Sentry TAURI-RUST-8NM: the process-global people store was
|
||||
/// never seeded at boot, so `store::get()` (and every `people_*` tool /
|
||||
/// controller) always failed with "people store not initialised". Boot now
|
||||
/// calls `init_from_workspace`; verify it seeds the global, creates the on-disk
|
||||
/// db, and is idempotent.
|
||||
#[tokio::test]
|
||||
async fn init_from_workspace_seeds_global_store() {
|
||||
use crate::openhuman::people::store;
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let store = store::init_from_workspace(tmp.path()).await.unwrap();
|
||||
assert!(
|
||||
tmp.path().join("people").join("people.db").exists(),
|
||||
"boot seed must create <workspace>/people/people.db"
|
||||
);
|
||||
|
||||
// The previously-dead global is now reachable — this is the fix.
|
||||
let via_global = store::get().expect("people store reachable after boot seed");
|
||||
assert!(Arc::ptr_eq(&store, &via_global));
|
||||
|
||||
// Idempotent: a second seed returns the same instance, never errors.
|
||||
let again = store::init_from_workspace(tmp.path()).await.unwrap();
|
||||
assert!(Arc::ptr_eq(&store, &again));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn person_id_uuid_format() {
|
||||
let id = PersonId::new();
|
||||
|
||||
Reference in New Issue
Block a user