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}