Skip to main content

subc_protocol/
session.rs

1//! Session-attach data-plane wire contract.
2//!
3//! subc has two distinct channel-0 handshakes. Module registration is the
4//! module-to-subc `HELLO`/`HELLO_ACK` handshake that registers the manifest and
5//! liveness. Session attach is the client-to-subc-to-module request/response
6//! handshake that binds one `(project_root, harness, session)` triple to a
7//! route channel.
8//!
9//! Session attach is vetoed by the module: subc relays an [`AttachRelay`] to
10//! the singleton module, commits the route only after an accepted
11//! [`AttachRelayResponse`], and returns module rejection as an [`ErrorBody`]
12//! `ERROR` frame. The triple binds once at attach time; subsequent data-plane
13//! frames carry only the envelope `channel` and opaque body bytes.
14//!
15//! Config is forwarded as an ordered, provenance-tagged tier list. subc treats
16//! every config document as opaque text and preserves `tier`, `source`, and
17//! `doc` exactly from [`AttachRequest`] to [`AttachRelay`]; it never parses,
18//! merges, partitions, or relabels config in transit.
19//!
20//! [`ErrorBody`]: crate::ErrorBody
21
22use std::path::PathBuf;
23
24use serde::{Deserialize, Serialize};
25
26/// One provenance-tagged config tier supplied during session attach.
27#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
28pub struct ConfigTier {
29    /// Trust label for this tier.
30    ///
31    /// Known v1 values are `"user"` and `"project"`, but this is intentionally
32    /// an open string so a future tier is a value, not a struct change.
33    ///
34    /// SECURITY INVARIANT: the label is stamped from `source` (the file path
35    /// that was read), never derived from `doc` content. subc preserves labels
36    /// exactly and never relabels them in transit.
37    pub tier: String,
38    /// Absolute path the document was read from, used for provenance and by AFT
39    /// to infer format/JSONC behavior and produce diagnostics.
40    pub source: String,
41    /// Verbatim config-source text.
42    ///
43    /// subc treats this as opaque bytes-as-text and does not interpret, parse,
44    /// merge, or validate it. AFT owns parsing, including JSONC handling.
45    pub doc: String,
46}
47
48/// Client-originated channel-0 control RPC body for binding a harness session
49/// to a module route.
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
51pub struct AttachRequest {
52    pub project_root: PathBuf,
53    pub harness: String,
54    pub session: String,
55    /// Ordered config tiers; precedence is list order, with later tiers winning.
56    pub config: Vec<ConfigTier>,
57}
58
59/// subc's channel-0 response body for an accepted session attach.
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
61pub struct AttachAck {
62    pub route_channel: u16,
63}
64
65/// subc-to-module channel-0 control RPC body asking the singleton module to bind
66/// a route channel.
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
68pub struct AttachRelay {
69    pub route_channel: u16,
70    pub project_root: PathBuf,
71    pub harness: String,
72    pub session: String,
73    /// Ordered config tiers forwarded from [`AttachRequest`] unchanged; subc
74    /// never merges, partitions, parses, or relabels config.
75    pub config: Vec<ConfigTier>,
76}
77
78/// Module response body for an accepted attach relay.
79///
80/// A rejected attach is returned as an [`ErrorBody`] `ERROR` frame.
81///
82/// [`ErrorBody`]: crate::ErrorBody
83#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
84pub struct AttachRelayResponse {
85    pub accept: bool,
86}
87
88/// subc-to-module channel-0 control RPC body telling the module a route channel
89/// is gone after client `GOODBYE` or client drop.
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
91pub struct DetachRelay {
92    pub route_channel: u16,
93}