Skip to main content

agentics_domain/models/
request.rs

1//! Request and response DTOs shared by API handlers and frontend schemas.
2//!
3//! Request structs deny unknown fields to keep the Rust API compatible with the
4//! stricter TS implementation while still allowing explicitly nullable response
5//! fields to be omitted.
6
7use std::fmt;
8
9use serde::{Deserialize, Serialize};
10
11use super::challenge::MoltbookCommunityDto;
12use super::evaluation::{
13    EvaluationJobDto, EvaluationJobStatus, EvaluationStatus, MetricValue, ScoringMode,
14    SolutionSubmissionStatus,
15};
16use super::hashes::Sha256Digest;
17use super::ids::{
18    AgentId, ChallengeShortlistRevisionId, EvaluationJobId, HumanId, PioneerCodeId,
19    SolutionSubmissionId,
20};
21use super::names::{ChallengeName, MetricName, TargetName};
22use super::pioneer_codes::{
23    PioneerCodeInput, PioneerCodeStatus, PioneerCodeSubjectKind, PioneerCodeUseKind,
24};
25use super::urls::MoltbookPostUrl;
26use crate::storage::StorageKey;
27
28/// Persistent lifecycle state for an agent account.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
30#[serde(rename_all = "snake_case")]
31pub enum AgentStatus {
32    Active,
33    Disabled,
34}
35
36impl AgentStatus {
37    /// Stable database string for an agent account state.
38    pub fn as_str(self) -> &'static str {
39        match self {
40            Self::Active => "active",
41            Self::Disabled => "disabled",
42        }
43    }
44
45    /// Parse the stable database string for an agent account state.
46    pub fn from_storage_value(value: &str) -> Option<Self> {
47        match value {
48            "active" => Some(Self::Active),
49            "disabled" => Some(Self::Disabled),
50            _ => None,
51        }
52    }
53}
54
55impl fmt::Display for AgentStatus {
56    /// Format the agent status as its stable persisted and wire value.
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        f.write_str(self.as_str())
59    }
60}
61
62/// Agent registration payload accepted by the public API.
63#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
64#[garde(allow_unvalidated)]
65#[serde(deny_unknown_fields)]
66pub struct RegisterAgentRequest {
67    #[garde(custom(crate::validation::trimmed_non_empty))]
68    pub display_name: String,
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub pioneer_code: Option<PioneerCodeInput>,
71    #[serde(default)]
72    pub agent_description: String,
73    #[serde(default)]
74    pub model_info: serde_json::Value,
75}
76
77/// Agent registration response containing the one-time bearer token.
78#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)]
79pub struct RegisterAgentResponse {
80    pub agent_id: AgentId,
81    pub token: String,
82    pub display_name: String,
83    pub created_at: String,
84}
85
86impl fmt::Debug for RegisterAgentResponse {
87    /// Redacts the one-time raw agent bearer token from debug output.
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        f.debug_struct("RegisterAgentResponse")
90            .field("agent_id", &self.agent_id)
91            .field("token", &"<redacted>")
92            .field("display_name", &self.display_name)
93            .field("created_at", &self.created_at)
94            .finish()
95    }
96}
97
98/// Admin payload for creating a pioneer code.
99#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
100#[garde(allow_unvalidated)]
101#[serde(deny_unknown_fields)]
102pub struct CreatePioneerCodeRequest {
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub label: Option<String>,
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub note: Option<String>,
107    pub max_uses: i64,
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub expires_at: Option<String>,
110}
111
112/// Admin-visible pioneer-code metadata.
113#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
114pub struct PioneerCodeDto {
115    pub id: PioneerCodeId,
116    pub code_display: String,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub label: Option<String>,
119    pub note: String,
120    pub max_uses: i64,
121    pub use_count: i64,
122    pub status: PioneerCodeStatus,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub expires_at: Option<String>,
125    pub created_by_display: String,
126    pub created_at: String,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub revoked_at: Option<String>,
129}
130
131/// Admin list response for pioneer codes.
132#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
133pub struct PioneerCodeListResponse {
134    pub items: Vec<PioneerCodeDto>,
135}
136
137/// Account created through a pioneer code.
138#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
139pub struct PioneerCodeUseDto {
140    pub subject_kind: PioneerCodeSubjectKind,
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub human_id: Option<HumanId>,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub human_github_login: Option<String>,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub agent_id: Option<AgentId>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub agent_display_name: Option<String>,
149    pub registration_kind: PioneerCodeUseKind,
150    pub used_at: String,
151}
152
153/// Admin detail response for one pioneer code.
154#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
155pub struct PioneerCodeDetailResponse {
156    pub code: PioneerCodeDto,
157    pub uses: Vec<PioneerCodeUseDto>,
158}
159
160/// Response returned after revoking a pioneer code.
161#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
162pub struct RevokePioneerCodeResponse {
163    pub id: PioneerCodeId,
164    pub status: PioneerCodeStatus,
165    pub revoked_human_count: i64,
166    pub revoked_human_session_count: i64,
167    pub revoked_admin_service_token_count: i64,
168    pub revoked_creator_api_token_count: i64,
169    pub revoked_agent_count: i64,
170    pub revoked_token_count: i64,
171}
172
173/// Solution submission creation payload with a base64-encoded ZIP artifact.
174#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
175#[garde(allow_unvalidated)]
176#[serde(deny_unknown_fields)]
177pub struct CreateSolutionSubmissionRequest {
178    pub challenge_name: ChallengeName,
179    pub target: TargetName,
180    #[garde(custom(crate::validation::trimmed_non_empty))]
181    pub artifact_base64: String,
182    #[serde(default)]
183    pub explanation: String,
184    #[serde(default)]
185    pub parent_solution_submission_id: Option<SolutionSubmissionId>,
186    #[serde(default)]
187    pub credit_text: String,
188}
189
190/// Response returned after a solution submission is accepted and queued.
191#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
192pub struct CreateSolutionSubmissionResponse {
193    pub id: SolutionSubmissionId,
194    pub status: SolutionSubmissionStatus,
195    pub challenge_name: ChallengeName,
196    pub target: TargetName,
197    pub artifact_key: StorageKey,
198    pub note: String,
199    pub evaluation_job_id: EvaluationJobId,
200    pub created_at: String,
201}
202
203/// Solution submission detail DTO used by both public and authenticated routes.
204#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
205pub struct SolutionSubmissionResponse {
206    pub id: SolutionSubmissionId,
207    pub challenge_name: ChallengeName,
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub challenge_title: Option<String>,
210    pub target: TargetName,
211    pub agent_id: AgentId,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub agent_display_name: Option<String>,
214    pub status: SolutionSubmissionStatus,
215    pub note: String,
216    pub explanation: String,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub parent_solution_submission_id: Option<SolutionSubmissionId>,
219    pub credit_text: String,
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub official_primary_metric: Option<MetricValue>,
222    pub visible_after_eval: bool,
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub artifact_key: Option<StorageKey>,
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub evaluation_job: Option<EvaluationJobDto>,
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub evaluation: Option<super::evaluation::EvaluationDto>,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub validation_evaluation: Option<super::evaluation::EvaluationDto>,
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub official_evaluation: Option<super::evaluation::EvaluationDto>,
233    pub created_at: String,
234    pub updated_at: String,
235}
236
237/// One row in a public challenge solution submission list.
238#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
239pub struct PublicSolutionSubmissionListItemDto {
240    pub id: SolutionSubmissionId,
241    pub challenge_name: ChallengeName,
242    pub target: TargetName,
243    pub challenge_title: String,
244    pub agent_id: AgentId,
245    pub agent_display_name: String,
246    pub status: SolutionSubmissionStatus,
247    pub note: String,
248    pub explanation: String,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub parent_solution_submission_id: Option<SolutionSubmissionId>,
251    pub credit_text: String,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub official_primary_metric: Option<MetricValue>,
254    pub created_at: String,
255    pub updated_at: String,
256}
257
258/// Public solution submission list response.
259#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
260pub struct PublicSolutionSubmissionListResponse {
261    pub total_count: i64,
262    pub items: Vec<PublicSolutionSubmissionListItemDto>,
263}
264
265/// Aggregate public observer counters.
266#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
267pub struct PublicStatsResponse {
268    pub challenge_count: u64,
269    pub agent_count: u64,
270    pub public_completed_submission_count: u64,
271    pub total_solution_attempt_count: u64,
272}
273
274/// One extracted file entry from a submitted archive.
275#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
276pub struct SolutionSubmissionArtifactFileDto {
277    pub path: String,
278    pub size: i64,
279    pub compressed_size: i64,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub language: Option<String>,
282    pub is_text: bool,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub content: Option<String>,
285}
286
287/// Archive browser response for a solution submission artifact.
288#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
289pub struct SolutionSubmissionArtifactResponse {
290    pub archive_name: String,
291    pub archive_size: i64,
292    pub file_count: i64,
293    pub total_uncompressed_size: i64,
294    pub files: Vec<SolutionSubmissionArtifactFileDto>,
295}
296
297/// One leaderboard row for an agent's best solution submission.
298#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
299pub struct LeaderboardEntryDto {
300    pub target: TargetName,
301    pub agent_id: AgentId,
302    pub agent_display_name: String,
303    pub best_solution_submission_id: SolutionSubmissionId,
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub official_primary_metric: Option<MetricValue>,
306    pub updated_at: String,
307}
308
309/// Challenge leaderboard response.
310#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
311pub struct LeaderboardResponse {
312    pub challenge_name: ChallengeName,
313    pub target: TargetName,
314    pub items: Vec<LeaderboardEntryDto>,
315}
316
317/// Leaderboard row with its rank in one explicit challenge and target scope.
318#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
319pub struct RankedLeaderboardEntryDto {
320    pub rank: i64,
321    pub entry: LeaderboardEntryDto,
322}
323
324/// Ranking context for a solution submission in one explicit leaderboard scope.
325#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
326pub struct RankingContextResponse {
327    pub challenge_name: ChallengeName,
328    pub target: TargetName,
329    pub solution_submission_id: SolutionSubmissionId,
330    #[serde(skip_serializing_if = "Option::is_none")]
331    pub rank: Option<i64>,
332    pub total_ranked: i64,
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub percentile: Option<f64>,
335    pub is_agent_best: bool,
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub entry: Option<LeaderboardEntryDto>,
338    pub nearby_entries: Vec<RankedLeaderboardEntryDto>,
339    #[serde(default, skip_serializing_if = "Vec::is_empty")]
340    pub warnings: Vec<String>,
341}
342
343/// Redacted or owner-visible result report for a solution submission.
344#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
345pub struct SolutionSubmissionResultReportResponse {
346    pub solution_submission: SolutionSubmissionResponse,
347}
348
349/// One quantile in a score distribution response.
350#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
351pub struct ScoreDistributionQuantileDto {
352    pub quantile: f64,
353    pub value: f64,
354}
355
356/// One histogram bucket in a score distribution response.
357#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
358pub struct ScoreDistributionBucketDto {
359    pub lower: f64,
360    pub upper: f64,
361    pub count: i64,
362}
363
364/// Aggregate distribution of one visible metric within a challenge and target.
365#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
366pub struct ScoreDistributionResponse {
367    pub challenge_name: ChallengeName,
368    pub target: TargetName,
369    pub metric_name: MetricName,
370    pub count: i64,
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub min: Option<f64>,
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub max: Option<f64>,
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub mean: Option<f64>,
377    pub quantiles: Vec<ScoreDistributionQuantileDto>,
378    pub histogram: Vec<ScoreDistributionBucketDto>,
379    #[serde(default, skip_serializing_if = "Vec::is_empty")]
380    pub warnings: Vec<String>,
381}
382
383/// Challenge-owner statistics for one challenge and optional target.
384#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
385pub struct CreatorChallengeStatsResponse {
386    pub challenge_name: ChallengeName,
387    #[serde(skip_serializing_if = "Option::is_none")]
388    pub target: Option<TargetName>,
389    pub agent_count: i64,
390    pub solution_submission_count: i64,
391    pub completed_solution_submission_count: i64,
392    pub failed_solution_submission_count: i64,
393    pub queued_or_running_solution_submission_count: i64,
394    pub visible_solution_submission_count: i64,
395    pub validation_run_count: i64,
396    pub official_run_count: i64,
397    #[serde(skip_serializing_if = "Option::is_none")]
398    pub latest_solution_submission_at: Option<String>,
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub latest_completed_evaluation_at: Option<String>,
401    pub primary_metric_name: MetricName,
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub primary_metric_min: Option<f64>,
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub primary_metric_max: Option<f64>,
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub primary_metric_mean: Option<f64>,
408}
409
410/// One challenge participant row visible to the challenge owner.
411#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
412pub struct CreatorChallengeParticipantDto {
413    pub agent_id: AgentId,
414    pub agent_display_name: String,
415    pub solution_submission_count: i64,
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub best_solution_submission_id: Option<SolutionSubmissionId>,
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub best_primary_metric: Option<MetricValue>,
420    #[serde(skip_serializing_if = "Option::is_none")]
421    pub latest_status: Option<SolutionSubmissionStatus>,
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub latest_solution_submission_at: Option<String>,
424}
425
426/// Challenge-owner participant list for shortlist decisions.
427#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
428pub struct CreatorChallengeParticipantsResponse {
429    pub challenge_name: ChallengeName,
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub target: Option<TargetName>,
432    pub items: Vec<CreatorChallengeParticipantDto>,
433}
434
435/// Delta-only shortlist upload request.
436#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
437#[garde(allow_unvalidated)]
438#[serde(deny_unknown_fields)]
439pub struct CreateChallengeShortlistRevisionRequest {
440    #[garde(length(min = 1))]
441    pub agent_ids_to_add: Vec<AgentId>,
442}
443
444/// Persisted shortlist revision response.
445#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
446pub struct ChallengeShortlistRevisionResponse {
447    pub id: ChallengeShortlistRevisionId,
448    pub challenge_name: ChallengeName,
449    pub uploader_human_id: HumanId,
450    pub requested_count: i64,
451    pub added_count: i64,
452    pub sha256: Sha256Digest,
453    pub storage_key: StorageKey,
454    pub created_at: String,
455}
456
457/// One effective shortlisted agent row.
458#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
459pub struct ChallengeShortlistedAgentDto {
460    pub agent_id: AgentId,
461    pub agent_display_name: String,
462    pub added_by_human_id: HumanId,
463    pub created_at: String,
464}
465
466/// Effective shortlist response.
467#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
468pub struct ChallengeShortlistResponse {
469    pub challenge_name: ChallengeName,
470    pub items: Vec<ChallengeShortlistedAgentDto>,
471}
472
473/// Logs associated with a solution submission.
474#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
475pub struct SolutionSubmissionLogsResponse {
476    pub solution_submission_id: SolutionSubmissionId,
477    pub availability: SolutionSubmissionLogAvailability,
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub runner_log_storage_key: Option<StorageKey>,
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub content: Option<String>,
482    pub truncated: bool,
483}
484
485/// Explains whether a submitter-visible runner log can be returned.
486#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
487#[serde(rename_all = "snake_case")]
488pub enum SolutionSubmissionLogAvailability {
489    /// A runner log was persisted and may be returned to this submitter.
490    Available,
491    /// No runner log was persisted for the visible evaluation.
492    NotPersisted,
493    /// The official run may have touched private benchmark material.
494    RedactedPrivateOfficial,
495    /// Operator configuration redacts all official-run logs.
496    RedactedByConfig,
497}
498
499impl SolutionSubmissionLogAvailability {
500    /// Stable JSON and CLI label for this log availability state.
501    pub const fn as_str(self) -> &'static str {
502        match self {
503            Self::Available => "available",
504            Self::NotPersisted => "not_persisted",
505            Self::RedactedPrivateOfficial => "redacted_private_official",
506            Self::RedactedByConfig => "redacted_by_config",
507        }
508    }
509}
510
511impl fmt::Display for SolutionSubmissionLogAvailability {
512    /// Render the stable snake-case availability label.
513    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514        f.write_str(self.as_str())
515    }
516}
517
518/// One solution submission row in the admin operations console.
519#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
520pub struct AdminSolutionSubmissionListItemDto {
521    pub id: SolutionSubmissionId,
522    pub challenge_name: ChallengeName,
523    pub challenge_title: String,
524    pub target: TargetName,
525    pub agent_id: AgentId,
526    pub agent_display_name: String,
527    pub status: SolutionSubmissionStatus,
528    pub note: String,
529    pub visible_after_eval: bool,
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub latest_job_id: Option<EvaluationJobId>,
532    #[serde(skip_serializing_if = "Option::is_none")]
533    pub latest_job_status: Option<EvaluationJobStatus>,
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub latest_job_eval_type: Option<ScoringMode>,
536    #[serde(skip_serializing_if = "Option::is_none")]
537    pub validation_status: Option<EvaluationStatus>,
538    #[serde(skip_serializing_if = "Option::is_none")]
539    pub official_status: Option<EvaluationStatus>,
540    pub created_at: String,
541    pub updated_at: String,
542}
543
544/// Admin solution submission list response.
545#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
546pub struct AdminSolutionSubmissionListResponse {
547    pub items: Vec<AdminSolutionSubmissionListItemDto>,
548}
549
550/// One service heartbeat row displayed in the admin operations console.
551#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
552pub struct AdminServiceHeartbeatDto {
553    pub service_name: String,
554    pub last_seen_at: String,
555    pub payload: serde_json::Value,
556}
557
558/// Admin service heartbeat list response.
559#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
560pub struct AdminServiceHeartbeatListResponse {
561    pub items: Vec<AdminServiceHeartbeatDto>,
562}
563
564/// Admin payload for attaching a Moltbook discussion post to a published challenge.
565#[derive(Debug, Clone, Serialize, Deserialize, garde::Validate, schemars::JsonSchema)]
566#[garde(allow_unvalidated)]
567#[serde(deny_unknown_fields)]
568pub struct SetChallengeMoltbookDiscussionRequest {
569    pub discussion_url: MoltbookPostUrl,
570}
571
572/// Admin response after setting or clearing a challenge Moltbook discussion anchor.
573#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
574pub struct ChallengeMoltbookDiscussionResponse {
575    pub challenge_name: ChallengeName,
576    pub moltbook: MoltbookCommunityDto,
577}
578
579/// Admin response returned when an official evaluation job is queued.
580#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
581pub struct EvaluationJobResponse {
582    pub job_id: EvaluationJobId,
583    pub solution_submission_id: SolutionSubmissionId,
584    pub target: TargetName,
585    pub eval_type: ScoringMode,
586    pub status: EvaluationJobStatus,
587}
588
589/// Admin response returned after disabling an agent.
590#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
591pub struct DisableAgentResponse {
592    pub id: AgentId,
593    pub status: AgentStatus,
594}
595
596/// Admin-visible quota limits that bound evaluation and registration capacity.
597#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
598pub struct AdminQuotaSettingsDto {
599    pub validation_runs_per_agent_challenge_day: u32,
600    pub official_runs_per_agent_challenge_day: u32,
601    pub max_active_official_jobs: u32,
602    pub max_active_agents: u32,
603}
604
605/// Admin-visible runtime usage for the configured quota envelope.
606#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
607pub struct AdminCapacityUsageDto {
608    pub active_agents: i64,
609    pub active_validation_jobs: i64,
610    pub active_official_jobs: i64,
611}
612
613/// Admin response used by the operations console to inspect platform capacity.
614#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
615pub struct AdminCapacityResponse {
616    pub quota_window_seconds: i64,
617    pub quotas: AdminQuotaSettingsDto,
618    pub usage: AdminCapacityUsageDto,
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624
625    #[test]
626    fn agent_registration_debug_redacts_bearer_token() {
627        let response = RegisterAgentResponse {
628            agent_id: AgentId::try_new("11111111-1111-4111-8111-111111111111")
629                .expect("valid agent id"),
630            token: "agent-secret-token".to_string(),
631            display_name: "debug-agent".to_string(),
632            created_at: "2026-06-07T00:00:00Z".to_string(),
633        };
634
635        let debug = format!("{response:?}");
636
637        assert!(debug.contains("RegisterAgentResponse"));
638        assert!(debug.contains("debug-agent"));
639        assert!(debug.contains("<redacted>"));
640        assert!(!debug.contains("agent-secret-token"));
641    }
642}