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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//! `GET /assistant` and `GET /assistant/document` — the built-in assistant.
//!
//! The ops console is served from the binary at `/`; this is the same doctrine
//! for the assistant workflow. `/assistant` answers what assistant this binary
//! carries and whether it is the version the engine would run right now;
//! `/assistant/document` serves the document's own bytes, so an operator with
//! no repository checkout can still stand up the worker that serves its queue.
//!
//! # Authorization
//!
//! Behind the same [`HttpCaller`] extractor as every data route, for the reason
//! `/build` is: the description carries the engine's routing standing for the
//! assistant type, which is deployment state, not public information.
//!
//! # 🔴 Probe the BODY, never the status
//!
//! The ops-console SPA catch-all answers `200 text/html` for any unmatched
//! path, so a binary WITHOUT these routes still answers 200 here. A probe that
//! reads only the status code cannot tell an assistant-carrying build from one
//! without it; read the content type and the body.
use aion_proto::WireError;
use axum::{
Json,
extract::State,
http::{StatusCode, header},
response::{IntoResponse, Response},
};
use super::auth::HttpCaller;
use super::error::HttpWireError;
use crate::ServerState;
use crate::assistant::{AssistantDescriptor, describe, embedded_assistant};
/// `GET /assistant`.
pub(crate) async fn assistant_descriptor(
State(state): State<ServerState>,
HttpCaller(caller): HttpCaller,
) -> Result<Json<AssistantDescriptor>, HttpWireError> {
let embedded = embedded_assistant().map_err(|error| {
HttpWireError(WireError::backend(format!(
"this server carries no usable built-in assistant: {error}"
)))
})?;
let engine = state.engine().map_err(|error| {
HttpWireError(
WireError::backend(format!(
"the engine is not available to describe the built-in assistant: {error}"
))
.with_error_type("Engine"),
)
})?;
// The description is read for THIS caller: its grant rows report what this
// request's own resolved identity holds, and its `default_harness` is this
// caller's own last pick — never a deployment-wide answer.
let described = describe(
embedded,
engine.as_ref(),
state.assistant_sessions(),
&caller,
)
.await
.map_err(|error| {
HttpWireError(
WireError::backend(format!(
"the built-in assistant could not be described: {error}"
))
.with_error_type("AssistantDescriptionFailed"),
)
})?;
Ok(Json(described))
}
/// `GET /assistant/document` — the embedded AWL source, verbatim.
pub(crate) async fn assistant_document(HttpCaller(_caller): HttpCaller) -> Response {
match embedded_assistant() {
// The bytes are served even though the descriptor is derived from the
// same source: an operator standing up a worker needs the document
// itself, and it is the artifact every other answer is derived from.
Ok(embedded) => (
[(
header::CONTENT_TYPE,
axum::http::HeaderValue::from_static("text/plain; charset=utf-8"),
)],
embedded.source(),
)
.into_response(),
Err(error) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(WireError::backend(format!(
"this server carries no usable built-in assistant: {error}"
))),
)
.into_response(),
}
}
#[cfg(test)]
#[path = "assistant_tests.rs"]
mod assistant_tests;