M0: scaffold Cargo workspace, core types, event bus, permission engine, storage actor
Seven-crate workspace per docs/01-architecture.md. harness-core gets the domain types (Session/Message/Part/ToolState), a broadcast EventBus, the last-match-wins wildcard permission evaluator, and a SQLite storage actor (dedicated thread + mpsc, JSON-blob rows) with roundtrip tests. All other crates are compiling stubs. CI runs fmt/clippy -D warnings/test.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# 08 — Storage
|
||||
|
||||
`harness-core/src/store/`. SQLite per project: `~/.local/share/ai-harness/db/<project-slug>.sqlite` (slug = sanitized worktree path + hash), WAL mode.
|
||||
|
||||
## Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE session (id TEXT PRIMARY KEY, parent_id TEXT, created_at INTEGER, updated_at INTEGER,
|
||||
data TEXT NOT NULL); -- full Session JSON
|
||||
CREATE TABLE message (id TEXT PRIMARY KEY, session_id TEXT NOT NULL, created_at INTEGER,
|
||||
data TEXT NOT NULL);
|
||||
CREATE TABLE part (id TEXT PRIMARY KEY, message_id TEXT NOT NULL, session_id TEXT NOT NULL,
|
||||
idx INTEGER NOT NULL, data TEXT NOT NULL);
|
||||
CREATE TABLE job (task_id TEXT PRIMARY KEY, parent_session_id TEXT NOT NULL, data TEXT NOT NULL);
|
||||
CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT); -- schema_version, cache stamps
|
||||
|
||||
CREATE INDEX message_session ON message(session_id);
|
||||
CREATE INDEX part_message ON part(message_id);
|
||||
CREATE INDEX session_parent ON session(parent_id);
|
||||
```
|
||||
|
||||
JSON `data` columns (opencode's approach): schema evolution without a migration per field. IDs are ULIDs, so `ORDER BY id` is chronological.
|
||||
|
||||
## Storage actor
|
||||
|
||||
One dedicated thread owns the `rusqlite::Connection`; commands arrive over `mpsc::Sender<StoreCmd>`, each carrying a `oneshot` reply channel. `store::api` exposes typed async facades: `upsert_session`, `upsert_message`, `upsert_part`, `messages(session)`, `parts(message)`, `sessions()`, `jobs(parent)`, …
|
||||
|
||||
Rationale vs sqlx/async drivers: single writer, embedded DB, tiny schema — an actor thread is simpler, avoids async-driver complexity, and serializes writes for free.
|
||||
|
||||
## Write cadence
|
||||
|
||||
- Messages and tool-state transitions: written immediately.
|
||||
- Streaming text deltas: buffered by the processor's `DeltaFlusher` — flushed to the part row every 50 ms or 2 KB, while `AppEvent::PartDelta` goes to the UI immediately. UI is realtime; SQLite writes are throttled.
|
||||
- Synthetic parts (job board injection, reminders) are **not** persisted.
|
||||
|
||||
## Session resume
|
||||
|
||||
- Session list from the DB; opening a session replays its messages/parts into the TUI view.
|
||||
- Crash recovery: on load, an unfinished assistant message (no `finished`, no error) is marked aborted; in-flight tool parts become `Error { "interrupted" }`.
|
||||
- Job records reload from the `job` table so the board survives restarts (running jobs whose runs died are marked `Error` on load).
|
||||
Reference in New Issue
Block a user