Skip to main content

systemprompt_models/bridge/
manifest.rs

1//! Signed manifest wire format.
2//!
3//! [`SignedManifest`] is the JSON document the gateway returns from
4//! `GET /v1/bridge/manifest` and the bridge consumes to drive its
5//! plugin / skill / agent / managed-MCP sync. Every public type in
6//! this module is part of that wire contract — the gateway server
7//! (in `crates/entry/api`) emits these structs and the bridge
8//! deserialises them, so any change here is a wire-format change.
9//!
10//! Signing, signature verification, and manifest construction live in
11//! the bridge crate (`bin/bridge/src/gateway/manifest.rs`) alongside
12//! the gateway client. Those layers pull in `ed25519-dalek` and
13//! `serde_jcs` which are not appropriate dependencies for this
14//! foundation crate.
15
16use std::collections::BTreeMap;
17
18use serde::{Deserialize, Serialize};
19
20use crate::bridge::ids::{
21    ManagedMcpServerName, ManifestSignature, PluginId, Sha256Digest, SkillId, SkillName, ToolName,
22    ToolPolicy,
23};
24use crate::bridge::manifest_version::ManifestVersion;
25use systemprompt_identifiers::{AgentId, AgentName, TenantId, UserId, ValidatedUrl};
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct SignedManifest {
29    pub manifest_version: ManifestVersion,
30    pub issued_at: String,
31    pub not_before: String,
32    pub user_id: UserId,
33    pub tenant_id: Option<TenantId>,
34    #[serde(default)]
35    pub user: Option<UserInfo>,
36    pub plugins: Vec<PluginEntry>,
37    #[serde(default)]
38    pub skills: Vec<SkillEntry>,
39    #[serde(default)]
40    pub agents: Vec<AgentEntry>,
41    pub managed_mcp_servers: Vec<ManagedMcpServer>,
42    pub revocations: Vec<String>,
43    #[serde(default)]
44    pub enabled_hosts: Vec<String>,
45    /// Detached ed25519 signature of the canonicalised payload (every
46    /// field above this one). Always present on the wire even for
47    /// unsigned manifests, where it is the empty string.
48    pub signature: ManifestSignature,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct UserInfo {
53    pub id: UserId,
54    pub name: String,
55    pub email: String,
56    #[serde(default)]
57    pub display_name: Option<String>,
58    #[serde(default)]
59    pub roles: Vec<String>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct PluginEntry {
64    pub id: PluginId,
65    pub version: String,
66    pub sha256: Sha256Digest,
67    pub files: Vec<PluginFile>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct PluginFile {
72    pub path: String,
73    pub sha256: Sha256Digest,
74    pub size: u64,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct SkillEntry {
79    pub id: SkillId,
80    pub name: SkillName,
81    pub description: String,
82    pub file_path: String,
83    #[serde(default)]
84    pub tags: Vec<String>,
85    pub sha256: Sha256Digest,
86    pub instructions: String,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct AgentEntry {
91    pub id: AgentId,
92    pub name: AgentName,
93    pub display_name: String,
94    pub description: String,
95    pub version: String,
96    pub endpoint: String,
97    pub enabled: bool,
98    pub is_default: bool,
99    pub is_primary: bool,
100    #[serde(default)]
101    pub provider: Option<String>,
102    #[serde(default)]
103    pub model: Option<String>,
104    #[serde(default)]
105    pub mcp_servers: Vec<String>,
106    #[serde(default)]
107    pub skills: Vec<String>,
108    #[serde(default)]
109    pub tags: Vec<String>,
110    #[serde(default)]
111    pub system_prompt: Option<String>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ManagedMcpServer {
116    pub name: ManagedMcpServerName,
117    pub url: ValidatedUrl,
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub transport: Option<String>,
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub headers: Option<BTreeMap<String, String>>,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub oauth: Option<bool>,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub tool_policy: Option<BTreeMap<ToolName, ToolPolicy>>,
126}