systemprompt_models/services/system_admin.rs
1//! System-admin identity: the explicit, validated owner row that the
2//! platform attributes system-initiated work to (scheduler bootstrap jobs,
3//! gateway telemetry, default MCP server owners).
4//!
5//! Resolution is a one-shot operation performed during runtime bootstrap:
6//! the profile-supplied [`SystemAdminConfig`] is looked up against the
7//! `users` table, validated (active, has `admin` role), and the resulting
8//! [`SystemAdmin`] is handed to `AppContext`. From there, every consumer
9//! that needs the platform owner takes it as a constructor argument; the
10//! only exception is logging attribution, which parks the value in a
11//! cell scoped to `systemprompt_logging`.
12//!
13//! Copyright (c) systemprompt.io — Business Source License 1.1.
14//! See <https://systemprompt.io> for licensing details.
15
16use serde::{Deserialize, Serialize};
17use systemprompt_identifiers::{Email, UserId};
18
19/// Profile-supplied configuration for the platform owner. Must resolve at
20/// startup to an active user row carrying the `admin` role; the platform
21/// refuses to boot otherwise.
22#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
23#[serde(deny_unknown_fields)]
24pub struct SystemAdminConfig {
25 pub username: String,
26
27 /// Email `admin bootstrap` uses when it first creates the owner row.
28 /// Absent on already-bootstrapped installs; creation without it (or
29 /// `--email`) is refused rather than synthesized.
30 #[serde(default)]
31 pub email: Option<Email>,
32}
33
34/// Resolved system-admin handle threaded through `AppContext`. Holds the
35/// typed `UserId` of the actual `users` row, not a sentinel.
36#[derive(Debug, Clone)]
37pub struct SystemAdmin {
38 id: UserId,
39 username: String,
40}
41
42impl SystemAdmin {
43 #[must_use]
44 pub const fn new(id: UserId, username: String) -> Self {
45 Self { id, username }
46 }
47
48 #[must_use]
49 pub const fn id(&self) -> &UserId {
50 &self.id
51 }
52
53 #[must_use]
54 pub fn username(&self) -> &str {
55 &self.username
56 }
57}