Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions crates/edda-serve/src/api/analytics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ async fn get_recap(
Query(params): Query<RecapQuery>,
) -> Result<Json<RecapResponse>, AppError> {
if state.chronicle.is_none() {
return Err(anyhow::anyhow!("chronicle feature not enabled").into());
return Err(AppError::NotImplemented(
"chronicle feature not enabled".into(),
));
}

let anchor = if let Some(ref project) = params.project {
Expand Down Expand Up @@ -149,7 +151,9 @@ async fn get_recap_cached(
Query(params): Query<RecapCachedQuery>,
) -> Result<Json<RecapResponse>, AppError> {
if state.chronicle.is_none() {
return Err(anyhow::anyhow!("chronicle feature not enabled").into());
return Err(AppError::NotImplemented(
"chronicle feature not enabled".into(),
));
}

let anchor = if let Some(ref project) = params.project {
Expand Down Expand Up @@ -221,7 +225,9 @@ async fn get_overview(
State(state): State<Arc<AppState>>,
) -> Result<Json<OverviewResponse>, AppError> {
if state.chronicle.is_none() {
return Err(anyhow::anyhow!("chronicle feature not enabled").into());
return Err(AppError::NotImplemented(
"chronicle feature not enabled".into(),
));
}

let projects = list_projects();
Expand Down Expand Up @@ -293,7 +299,9 @@ async fn get_projects(
State(state): State<Arc<AppState>>,
) -> Result<Json<ProjectsResponse>, AppError> {
if state.chronicle.is_none() {
return Err(anyhow::anyhow!("chronicle feature not enabled").into());
return Err(AppError::NotImplemented(
"chronicle feature not enabled".into(),
));
}

let projects = list_projects();
Expand Down
9 changes: 4 additions & 5 deletions crates/edda-serve/src/api/auth.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::sync::Arc;
use std::time::Duration;

use anyhow::Context;
use axum::extract::rejection::JsonRejection;
use axum::extract::{Query, State};
use axum::http::HeaderMap;
Expand Down Expand Up @@ -126,7 +125,7 @@ async fn complete_pairing(
let event_id = format!("evt_{}", ulid::Ulid::new());

// Write device_pair event to ledger
let ledger = state.open_ledger().context("GET /pair")?;
let ledger = state.open_ledger()?;
let branch = ledger.head_branch()?;

let payload = serde_json::json!({
Expand Down Expand Up @@ -183,7 +182,7 @@ struct DeviceInfo {
async fn list_paired_devices(
State(state): State<Arc<AppState>>,
) -> Result<Json<Vec<DeviceInfo>>, AppError> {
let ledger = state.open_ledger().context("GET /api/pair/list")?;
let ledger = state.open_ledger()?;
let tokens = ledger.list_device_tokens()?;

let devices: Vec<DeviceInfo> = tokens
Expand Down Expand Up @@ -215,7 +214,7 @@ async fn revoke_device(
) -> Result<Json<serde_json::Value>, AppError> {
let Json(req) = body.map_err(|e| AppError::Validation(e.to_string()))?;

let ledger = state.open_ledger().context("POST /api/pair/revoke")?;
let ledger = state.open_ledger()?;

// Check the token exists *before* writing the ledger event
let existing = ledger.list_device_tokens()?;
Expand Down Expand Up @@ -273,7 +272,7 @@ async fn revoke_all_devices(
State(state): State<Arc<AppState>>,
) -> Result<Json<serde_json::Value>, AppError> {
let event_id = format!("evt_{}", ulid::Ulid::new());
let ledger = state.open_ledger().context("POST /api/pair/revoke-all")?;
let ledger = state.open_ledger()?;
let branch = ledger.head_branch()?;

let now = time::OffsetDateTime::now_utc();
Expand Down
9 changes: 4 additions & 5 deletions crates/edda-serve/src/api/briefs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::sync::Arc;

use anyhow::Context;
use axum::extract::{Path as AxumPath, Query, State};
use axum::response::IntoResponse;
use axum::routing::get;
Expand Down Expand Up @@ -35,7 +34,7 @@ struct ActorsListResponse {
async fn get_actors(
State(state): State<Arc<AppState>>,
) -> Result<Json<ActorsListResponse>, AppError> {
let ledger = state.open_ledger().context("GET /api/actors")?;
let ledger = state.open_ledger()?;
let cfg = policy::load_actors_from_dir(&ledger.paths.edda_dir)?;
let actors = cfg
.actors
Expand All @@ -58,7 +57,7 @@ async fn get_actor(
State(state): State<Arc<AppState>>,
AxumPath(name): AxumPath<String>,
) -> Result<Json<ActorResponse>, AppError> {
let ledger = state.open_ledger().context("GET /api/actors/:name")?;
let ledger = state.open_ledger()?;
let cfg = policy::load_actors_from_dir(&ledger.paths.edda_dir)?;
match cfg.actors.get(&name) {
Some(def) => Ok(Json(ActorResponse {
Expand Down Expand Up @@ -90,7 +89,7 @@ async fn get_briefs(
State(state): State<Arc<AppState>>,
Query(params): Query<BriefsQuery>,
) -> Result<Json<serde_json::Value>, AppError> {
let ledger = state.open_ledger().context("GET /api/briefs")?;
let ledger = state.open_ledger()?;
let briefs = ledger.list_task_briefs(params.status.as_deref(), params.intent.as_deref())?;

let items: Vec<serde_json::Value> = briefs
Expand Down Expand Up @@ -125,7 +124,7 @@ async fn get_brief(
State(state): State<Arc<AppState>>,
AxumPath(task_id): AxumPath<String>,
) -> Result<Json<serde_json::Value>, AppError> {
let ledger = state.open_ledger().context("GET /api/briefs/:task_id")?;
let ledger = state.open_ledger()?;
let brief = ledger
.get_task_brief(&task_id)?
.ok_or_else(|| AppError::NotFound(format!("task brief not found: {task_id}")))?;
Expand Down
5 changes: 2 additions & 3 deletions crates/edda-serve/src/api/drafts.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::path::Path;
use std::sync::Arc;

use anyhow::Context;
use axum::extract::rejection::JsonRejection;
use axum::extract::{Path as AxumPath, State};
use axum::http::{HeaderMap, StatusCode};
Expand Down Expand Up @@ -82,7 +81,7 @@ struct MinimalStage {
}

async fn get_drafts(State(state): State<Arc<AppState>>) -> Result<Json<DraftsResponse>, AppError> {
let ledger = state.open_ledger().context("GET /api/drafts")?;
let ledger = state.open_ledger()?;
let drafts_dir = &ledger.paths.drafts_dir;

if !drafts_dir.exists() {
Expand Down Expand Up @@ -280,7 +279,7 @@ async fn handle_draft_action(
action: &str,
body: &ApproveRequest,
) -> Result<Response, AppError> {
let ledger = state.open_ledger().context("POST /api/drafts/:id/action")?;
let ledger = state.open_ledger()?;
let _lock = WorkspaceLock::acquire(&ledger.paths)?;

// Read the draft
Expand Down
25 changes: 10 additions & 15 deletions crates/edda-serve/src/api/events.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::sync::Arc;

use anyhow::Context;
use axum::extract::rejection::JsonRejection;
use axum::extract::{Path as AxumPath, Query, State};
use axum::http::StatusCode;
Expand Down Expand Up @@ -40,7 +39,7 @@ struct LastCommit {
}

async fn get_status(State(state): State<Arc<AppState>>) -> Result<Json<StatusResponse>, AppError> {
let ledger = state.open_ledger().context("GET /api/status")?;
let ledger = state.open_ledger()?;
let head = ledger.head_branch()?;
let snap = rebuild_branch(&ledger, &head)?;

Expand Down Expand Up @@ -73,7 +72,7 @@ async fn get_context(
State(state): State<Arc<AppState>>,
Query(params): Query<ContextQuery>,
) -> Result<Json<ContextResponse>, AppError> {
let ledger = state.open_ledger().context("GET /api/context")?;
let ledger = state.open_ledger()?;
let head = ledger.head_branch()?;
let depth = params.depth.unwrap_or(5);
let text = render_context(&ledger, &head, DeriveOptions { depth })?;
Expand Down Expand Up @@ -110,7 +109,7 @@ async fn get_decisions(
crate::helpers::validate_iso8601(before).map_err(AppError::Validation)?;
}

let ledger = state.open_ledger().context("GET /api/decisions")?;
let ledger = state.open_ledger()?;
let q = params
.q
.as_deref()
Expand Down Expand Up @@ -195,7 +194,7 @@ async fn post_decisions_batch(
));
}

let ledger = state.open_ledger().context("POST /api/decisions/batch")?;
let ledger = state.open_ledger()?;
let mut results = Vec::with_capacity(body.queries.len());

for (i, sub) in body.queries.iter().enumerate() {
Expand Down Expand Up @@ -264,9 +263,7 @@ async fn get_decision_outcomes(
State(state): State<Arc<AppState>>,
AxumPath(event_id): AxumPath<String>,
) -> Result<Response, AppError> {
let ledger = state
.open_ledger()
.context("GET /api/decisions/:id/outcomes")?;
let ledger = state.open_ledger()?;
let outcomes = ledger.decision_outcomes(&event_id)?;

match outcomes {
Expand Down Expand Up @@ -321,9 +318,7 @@ async fn get_decision_chain(
Query(params): Query<ChainQuery>,
) -> Result<Json<ChainResponse>, AppError> {
let depth = params.depth.unwrap_or(3).min(10);
let ledger = state
.open_ledger()
.context("GET /api/decisions/:id/chain")?;
let ledger = state.open_ledger()?;

let (root, chain) = ledger
.causal_chain(&event_id, depth)?
Expand Down Expand Up @@ -397,7 +392,7 @@ async fn get_log(
State(state): State<Arc<AppState>>,
Query(params): Query<LogQuery>,
) -> Result<Json<LogResponse>, AppError> {
let ledger = state.open_ledger().context("GET /api/log")?;
let ledger = state.open_ledger()?;
let head = ledger.head_branch()?;
let limit = params.limit.unwrap_or(50);

Expand Down Expand Up @@ -463,7 +458,7 @@ async fn post_note(
) -> Result<impl IntoResponse, AppError> {
let Json(body) = body.map_err(|e| AppError::Validation(e.body_text()))?;

let ledger = state.open_ledger().context("POST /api/note")?;
let ledger = state.open_ledger()?;
let _lock = WorkspaceLock::acquire(&ledger.paths)?;

let branch = ledger.head_branch()?;
Expand Down Expand Up @@ -511,7 +506,7 @@ async fn post_decide(
let key = key.trim();
let value = value.trim();

let ledger = state.open_ledger().context("POST /api/decide")?;
let ledger = state.open_ledger()?;
let _lock = WorkspaceLock::acquire(&ledger.paths)?;

let branch = ledger.head_branch()?;
Expand Down Expand Up @@ -633,7 +628,7 @@ async fn post_karvi_event(
"decision_ref": body.decision_ref,
});

let ledger = state.open_ledger().context("POST /api/events/karvi")?;
let ledger = state.open_ledger()?;
let _lock = WorkspaceLock::acquire(&ledger.paths)?;
let branch = ledger.head_branch()?;
let parent_hash = ledger.last_event_hash()?;
Expand Down
15 changes: 8 additions & 7 deletions crates/edda-serve/src/api/metrics.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::sync::Arc;

use anyhow::Context;
use axum::extract::rejection::JsonRejection;
use axum::extract::{Path as AxumPath, Query, State};
use axum::routing::{get, post};
Expand Down Expand Up @@ -34,7 +33,7 @@ async fn get_quality_metrics(
after: params.after,
before: params.before,
};
let ledger = state.open_ledger().context("GET /api/metrics/quality")?;
let ledger = state.open_ledger()?;
let events = ledger.iter_events_by_type("execution_event")?;
let report = model_quality_from_events(&events, &range);
Ok(Json(report))
Expand Down Expand Up @@ -63,9 +62,7 @@ async fn get_controls_suggestions(
after: params.after,
before: params.before,
};
let ledger = state
.open_ledger()
.context("GET /api/controls/suggestions")?;
let ledger = state.open_ledger()?;
let events = ledger.iter_events_by_type("execution_event")?;
let report = model_quality_from_events(&events, &range);

Expand Down Expand Up @@ -170,7 +167,9 @@ async fn get_metrics_overview(
Query(params): Query<MetricsOverviewQuery>,
) -> Result<Json<MetricsOverviewResponse>, AppError> {
if state.chronicle.is_none() {
return Err(anyhow::anyhow!("chronicle feature not enabled").into());
return Err(AppError::NotImplemented(
"chronicle feature not enabled".into(),
));
}

let all_projects = list_projects();
Expand Down Expand Up @@ -268,7 +267,9 @@ async fn get_metrics_trends(
Query(params): Query<TrendsQuery>,
) -> Result<Json<TrendsResponse>, AppError> {
if state.chronicle.is_none() {
return Err(anyhow::anyhow!("chronicle feature not enabled").into());
return Err(AppError::NotImplemented(
"chronicle feature not enabled".into(),
));
}

let all_projects = list_projects();
Expand Down
4 changes: 1 addition & 3 deletions crates/edda-serve/src/api/policy.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use std::sync::Arc;

use anyhow::Context;
use axum::extract::{Path as AxumPath, Query, State};
use axum::routing::{get, post};
use axum::{Json, Router};
use serde::{Deserialize, Serialize};

use edda_core::policy;
use edda_ledger::Ledger;

use crate::error::AppError;
use crate::state::AppState;
Expand Down Expand Up @@ -208,7 +206,7 @@ async fn post_approval_check(

// Build ReviewBundle from request or from ledger
let bundle = if let Some(bundle_id) = &body.bundle_id {
let ledger = Ledger::open(&state.repo_root).context("POST /api/approval/check")?;
let ledger = state.open_ledger()?;
let Some(row) = ledger.get_bundle(bundle_id)? else {
return Err(AppError::NotFound(format!(
"Bundle '{}' not found",
Expand Down
13 changes: 6 additions & 7 deletions crates/edda-serve/src/api/snapshots.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::sync::Arc;

use anyhow::Context;
use axum::extract::rejection::JsonRejection;
use axum::extract::{Path as AxumPath, Query, State};
use axum::http::StatusCode;
Expand Down Expand Up @@ -63,7 +62,7 @@ async fn post_snapshot(
));
}

let ledger = state.open_ledger().context("POST /api/snapshot")?;
let ledger = state.open_ledger()?;
let _lock = WorkspaceLock::acquire(&ledger.paths)?;

let branch = ledger.head_branch()?;
Expand Down Expand Up @@ -165,7 +164,7 @@ async fn get_snapshots(
State(state): State<Arc<AppState>>,
Query(query): Query<SnapshotsQuery>,
) -> Result<impl IntoResponse, AppError> {
let ledger = state.open_ledger().context("GET /api/snapshots")?;
let ledger = state.open_ledger()?;
let rows = ledger.query_snapshots(
query.village_id.as_deref(),
query.engine_version.as_deref(),
Expand All @@ -187,7 +186,7 @@ async fn get_snapshots_by_hash(
State(state): State<Arc<AppState>>,
AxumPath(context_hash): AxumPath<String>,
) -> Result<impl IntoResponse, AppError> {
let ledger = state.open_ledger().context("GET /api/snapshots/:hash")?;
let ledger = state.open_ledger()?;
let rows = ledger.snapshots_by_context_hash(&context_hash)?;

if rows.is_empty() {
Expand Down Expand Up @@ -227,7 +226,7 @@ async fn get_village_stats(
crate::helpers::validate_iso8601(before).map_err(AppError::Validation)?;
}

let ledger = state.open_ledger().context("GET /api/villages/:id/stats")?;
let ledger = state.open_ledger()?;
let stats = ledger.village_stats(
&village_id,
params.after.as_deref(),
Expand Down Expand Up @@ -268,7 +267,7 @@ async fn get_patterns(
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default();

let ledger = state.open_ledger().context("GET /api/patterns")?;
let ledger = state.open_ledger()?;
let patterns = ledger.detect_village_patterns(village_id, &after_str, min_occurrences)?;
let total = patterns.len();

Expand All @@ -288,7 +287,7 @@ fn reconstruct_snapshot(
) -> Result<serde_json::Value, AppError> {
let event = ledger
.get_event(&row.event_id)?
.ok_or_else(|| AppError::Internal(anyhow::anyhow!("event {} not found", row.event_id)))?;
.ok_or_else(|| AppError::NotFound(format!("event {} not found", row.event_id)))?;

let payload = &event.payload;

Expand Down
Loading
Loading