Phase 9: Event journal — append-only mutation history

- journald crate: immutable event log for every data mutation
- Events: entity_type, entity_id, field, action, old_value, new_value,
  actor, source, workspace_id, timestamp
- In-memory buffer with configurable flush threshold (default 100 events)
- Flush writes events as Parquet to journal/ directory
- Query: GET /journal/history/{entity_id} — full history of any record
- Query: GET /journal/recent?limit=50 — latest events across all entities
- Convenience methods: record_insert, record_update, record_ingest
- Stats: GET /journal/stats — buffer size, persisted file count
- Manual flush: POST /journal/flush
- Per ADR-012: events are never modified or deleted

This is the single most important future-proofing decision.
Once history is lost, it's gone forever.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-03-27 09:09:33 -05:00
co-authored by Claude Opus 4.6
parent 3b695cd592
commit bf7cf96911
8 changed files with 499 additions and 1 deletions
+1
View File
@@ -11,6 +11,7 @@ queryd = { path = "../queryd" }
aibridge = { path = "../aibridge" }
ingestd = { path = "../ingestd" }
vectord = { path = "../vectord" }
journald = { path = "../journald" }
tokio = { workspace = true }
axum = { workspace = true }
serde = { workspace = true }
+5 -1
View File
@@ -34,6 +34,9 @@ async fn main() {
let cache = queryd::cache::MemCache::new(16 * 1024 * 1024 * 1024);
let engine = queryd::context::QueryEngine::new(registry.clone(), store.clone(), cache);
// Event journal — append-only mutation log (flush every 100 events)
let journal = journald::journal::Journal::new(store.clone(), 100);
// Workspace manager for agent-specific overlays
let workspace_mgr = queryd::workspace::WorkspaceManager::new(store.clone());
if let Err(e) = workspace_mgr.rebuild().await {
@@ -59,7 +62,8 @@ async fn main() {
ai_client: ai_client.clone(),
job_tracker: vectord::jobs::JobTracker::new(),
}))
.nest("/workspaces", queryd::workspace_service::router(workspace_mgr));
.nest("/workspaces", queryd::workspace_service::router(workspace_mgr))
.nest("/journal", journald::service::router(journal));
// Auth middleware (if enabled)
if config.auth.enabled {