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 crate::services::hooks::{HookCategory, HookEvent};
26use systemprompt_identifiers::{AgentId, AgentName, HookId, TenantId, UserId, ValidatedUrl};
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct SignedManifest {
30    pub manifest_version: ManifestVersion,
31    pub issued_at: String,
32    pub not_before: String,
33    pub user_id: UserId,
34    pub tenant_id: Option<TenantId>,
35    #[serde(default)]
36    pub user: Option<UserInfo>,
37    pub plugins: Vec<PluginEntry>,
38    #[serde(default)]
39    pub skills: Vec<SkillEntry>,
40    #[serde(default)]
41    pub agents: Vec<AgentEntry>,
42    #[serde(default)]
43    pub hooks: Vec<HookEntry>,
44    pub managed_mcp_servers: Vec<ManagedMcpServer>,
45    pub revocations: Vec<String>,
46    #[serde(default)]
47    pub enabled_hosts: Vec<String>,
48    /// Detached ed25519 signature of the canonicalised payload (every
49    /// field above this one). Always present on the wire even for
50    /// unsigned manifests, where it is the empty string.
51    pub signature: ManifestSignature,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct UserInfo {
56    pub id: UserId,
57    pub name: String,
58    pub email: String,
59    #[serde(default)]
60    pub display_name: Option<String>,
61    #[serde(default)]
62    pub roles: Vec<String>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct PluginEntry {
67    pub id: PluginId,
68    pub version: String,
69    pub sha256: Sha256Digest,
70    pub files: Vec<PluginFile>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct PluginFile {
75    pub path: String,
76    pub sha256: Sha256Digest,
77    pub size: u64,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct SkillEntry {
82    pub id: SkillId,
83    pub name: SkillName,
84    pub description: String,
85    pub file_path: String,
86    #[serde(default)]
87    pub tags: Vec<String>,
88    pub sha256: Sha256Digest,
89    pub instructions: String,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct AgentEntry {
94    pub id: AgentId,
95    pub name: AgentName,
96    pub display_name: String,
97    pub description: String,
98    pub version: String,
99    pub endpoint: String,
100    pub enabled: bool,
101    pub is_default: bool,
102    pub is_primary: bool,
103    #[serde(default)]
104    pub provider: Option<String>,
105    #[serde(default)]
106    pub model: Option<String>,
107    #[serde(default)]
108    pub mcp_servers: Vec<String>,
109    #[serde(default)]
110    pub skills: Vec<String>,
111    #[serde(default)]
112    pub tags: Vec<String>,
113    #[serde(default)]
114    pub system_prompt: Option<String>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct HookEntry {
119    pub id: HookId,
120    pub name: String,
121    pub description: String,
122    pub version: String,
123    pub event: HookEvent,
124    pub matcher: String,
125    pub command: String,
126    #[serde(default)]
127    pub is_async: bool,
128    pub category: HookCategory,
129    #[serde(default)]
130    pub tags: Vec<String>,
131    pub sha256: Sha256Digest,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct ManagedMcpServer {
136    pub name: ManagedMcpServerName,
137    pub url: ValidatedUrl,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub transport: Option<String>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub headers: Option<BTreeMap<String, String>>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub oauth: Option<bool>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub tool_policy: Option<BTreeMap<ToolName, ToolPolicy>>,
146}