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