1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//! `GET /v1/recognition/check` — the operator's window into the trust
//! (recognition) graph.
//!
//! TRQP recognition is a **per-DID query** against the upstream trust registry,
//! not a listable set — the recognition graph lives in the registry, and a VTC
//! asks "do I recognise issuer X?" one DID at a time (`recognise`). This admin
//! lookup exposes exactly that: enter an issuer / community DID, get back
//! whether this community recognises it (the same verdict that decides whether a
//! third-party invitation issuer is trusted, M2). The configured-registry status
//! comes from `GET /v1/health/diagnostics`.
use axum::Json;
use axum::extract::{Query, State};
use serde::{Deserialize, Serialize};
use tracing::warn;
use vti_common::auth::AdminAuth;
use vti_common::error::AppError;
use crate::server::AppState;
#[derive(Debug, Deserialize, utoipa::IntoParams)]
#[serde(rename_all = "camelCase")]
pub struct CheckQuery {
/// The issuer / community DID to test for recognition.
pub did: String,
}
#[derive(Debug, Serialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct RecognitionCheck {
/// Echo of the queried DID.
pub did: String,
/// Whether this community recognises the DID (TRQP `recognise`). `false`
/// when no registry is configured or the query came back not-found.
pub recognised: bool,
/// Whether a trust registry is configured at all (no-registry mode → every
/// foreign DID is unrecognised).
pub registry_configured: bool,
/// Set when the registry query failed (unreachable / parse) rather than
/// returning a clean recognised/not-recognised verdict.
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[utoipa::path(
get, path = "/recognition/check", tag = "recognition",
params(CheckQuery),
security(("bearer_jwt" = [])),
responses(
(status = 200, description = "Recognition verdict", body = RecognitionCheck),
(status = 403, description = "Caller is not an admin"),
),
)]
pub async fn check(
_auth: AdminAuth,
State(state): State<AppState>,
Query(q): Query<CheckQuery>,
) -> Result<Json<RecognitionCheck>, AppError> {
let registry_configured = state.registry_client.is_some();
let (recognised, error) = match state.registry_client.as_deref() {
Some(registry) => match registry.recognise(&q.did).await {
Ok(verdict) => (verdict, None),
Err(e) => {
warn!(did = %q.did, error = %e, "recognition check failed");
(false, Some(e.to_string()))
}
},
None => (false, None),
};
Ok(Json(RecognitionCheck {
did: q.did,
recognised,
registry_configured,
error,
}))
}