1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Frames the managed backend consumes on behalf of a client. The SDK never
// sends these directly. They live here so the encoding has one definition and
// the golden corpus pins them like every other frame.
use serde::{Deserialize, Serialize};
/// A forwarded managed query. Carries the authenticated identity the SDK cannot
/// set itself, plus the opaque request the SDK sent. CBOR-encoded, named fields.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ForwardedQuery {
/// Authenticated identity. Audit today, per-user stream scoping later.
/// Trusted: set by the server, not the client.
pub user_id: u32,
/// Originating client id, for logging and audit only.
pub client_id: u128,
/// `gen_ai.conversation.id` echoed for the audit log, never used to route.
pub correlation: Option<String>,
/// Opaque CBOR `QueryEnvelope` from the SDK, not decoded in transit.
#[serde(with = "crate::encoding::bin_bytes")]
pub query_envelope: Vec<u8>,
/// The caller's effective capability grants, stamped by the server. Empty
/// (the default, skipped on the wire) when authorization is not in effect.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub grants: Vec<crate::authz::Grant>,
}
/// A forwarded keyed managed command (registry browse, key-value, forks). Unlike
/// `ForwardedQuery`, several op kinds share one path, so the frame carries
/// `command_code` and the backend dispatches on it. `payload` is the opaque
/// CBOR request the SDK sent.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ForwardedCommand {
/// Authenticated identity, set by the server. The key-value store scopes its
/// rows by this `user_id`, and the SDK cannot set it.
pub user_id: u32,
/// Originating client id, for logging and audit only.
pub client_id: u128,
/// `gen_ai.conversation.id` echoed for the audit log, never used to route.
pub correlation: Option<String>,
/// Set by the server, like `user_id`: true when the caller holds a global
/// read permission. Widens the read-side ops (key-value scan/namespaces,
/// fork list) to every user's rows, while writes stay scoped to `user_id`
/// regardless. Defaults to false for a frame from an older server that
/// does not set it.
#[serde(default)]
pub read_all: bool,
/// The managed command code (browse, key-value, or fork block). The backend
/// dispatches on it.
pub command_code: u32,
/// Opaque CBOR request the SDK sent, not decoded in transit.
#[serde(with = "crate::encoding::bin_bytes")]
pub payload: Vec<u8>,
/// The caller's effective capability grants, stamped by the server. Empty
/// (the default, skipped on the wire) when authorization is not in effect.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub grants: Vec<crate::authz::Grant>,
}
#[cfg(all(test, feature = "cbor"))]
mod tests {
use super::*;
use crate::framing::{decode_named, encode_named};
// A contiguous byte-string run inside the encoded frame proves the opaque
// field rode as a CBOR byte string (major type 2), not an array of ints.
// The values are all >= 0x18, which an array would have to widen to two
// bytes each, so the byte string is also strictly smaller.
fn contains_byte_string(frame: &[u8], len: u8, fill: u8) -> bool {
let head = 0x40 | len; // byte string, length in the low 5 bits
let mut want = vec![head];
want.extend(std::iter::repeat_n(fill, len as usize));
frame.windows(want.len()).any(|w| w == want.as_slice())
}
#[test]
fn given_forwarded_query_when_encoded_then_envelope_rides_as_a_byte_string() {
let frame = encode_named(&ForwardedQuery {
user_id: 7,
client_id: 42,
correlation: Some("conv-1".to_owned()),
query_envelope: vec![0x90; 5],
grants: Vec::new(),
})
.expect("encodes");
assert!(
contains_byte_string(&frame, 5, 0x90),
"query_envelope must encode as a CBOR byte string, got {frame:02x?}"
);
}
#[test]
fn given_forwarded_command_when_encoded_then_payload_rides_as_a_byte_string() {
let frame = encode_named(&ForwardedCommand {
user_id: 7,
client_id: 42,
correlation: None,
read_all: true,
command_code: 1_000_000,
payload: vec![0x90; 5],
grants: Vec::new(),
})
.expect("encodes");
assert!(
contains_byte_string(&frame, 5, 0x90),
"payload must encode as a CBOR byte string, got {frame:02x?}"
);
}
#[test]
fn given_forwarded_frames_when_round_tripped_then_should_preserve_fields() {
let query = ForwardedQuery {
user_id: 1,
client_id: u128::MAX,
correlation: Some("c".to_owned()),
query_envelope: vec![0xff, 0x00, 0x18, 0x7f],
grants: Vec::new(),
};
let back: ForwardedQuery =
decode_named(&encode_named(&query).expect("encodes")).expect("decodes");
assert_eq!(back, query);
let command = ForwardedCommand {
user_id: 2,
client_id: 9,
correlation: None,
read_all: false,
command_code: 42,
payload: vec![0xde, 0xad, 0xbe, 0xef],
grants: Vec::new(),
};
let back: ForwardedCommand =
decode_named(&encode_named(&command).expect("encodes")).expect("decodes");
assert_eq!(back, command);
}
}