Skip to main content

turnframe_core/
read.rs

1//! Read-only tool contract for the bounded context loop (spec §11.2).
2//!
3//! Read tools query, search, retrieve, calculate and inspect. They never
4//! mutate. Every result carries a source label and a [`TrustLevel`] so retrieved
5//! content is treated as data, never as instructions (spec §25.3).
6
7use std::time::Duration;
8
9use schemars::{JsonSchema, Schema};
10use serde::{Deserialize, Serialize};
11
12use crate::error::SchemaCheckError;
13use crate::ids::{ReadRequestId, ReadToolKey};
14use crate::schema::validate_against;
15
16/// How sensitive the data a tool returns is.
17#[derive(
18    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
19)]
20#[serde(rename_all = "snake_case")]
21pub enum DataSensitivity {
22    /// Public.
23    Public,
24    /// Internal to the account.
25    Internal,
26    /// Confidential (personal or financial data).
27    Confidential,
28    /// Restricted (regulated, must not leave the region/provider allowlist).
29    Restricted,
30}
31
32/// How much a result can be trusted.
33#[derive(
34    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
35)]
36#[serde(rename_all = "snake_case")]
37pub enum TrustLevel {
38    /// Content from a third party or user upload; may contain injection.
39    Untrusted,
40    /// Retrieved from an approved but non-authoritative source.
41    Retrieved,
42    /// Authoritative application state.
43    Authoritative,
44}
45
46/// Declaration of a read-only tool (spec §11.2).
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub struct ReadToolDefinition {
49    /// Tool key.
50    pub key: ReadToolKey,
51    /// Description shown to the model.
52    pub description: String,
53    /// JSON Schema of the arguments.
54    pub input_schema: Schema,
55    /// JSON Schema of the output.
56    pub output_schema: Schema,
57    /// Sensitivity of returned data.
58    pub sensitivity: DataSensitivity,
59    /// Per-call timeout.
60    pub timeout: Duration,
61    /// Maximum result size; larger outputs are truncated and flagged.
62    pub max_result_bytes: usize,
63}
64
65impl ReadToolDefinition {
66    /// Validates arguments against `input_schema`.
67    pub fn validate_input(&self, arguments: &serde_json::Value) -> Result<(), SchemaCheckError> {
68        validate_against(&self.input_schema, arguments)
69    }
70
71    /// Validates an output against `output_schema`.
72    pub fn validate_output(&self, output: &serde_json::Value) -> Result<(), SchemaCheckError> {
73        validate_against(&self.output_schema, output)
74    }
75}
76
77/// A read request the model needs answered before it can interpret the turn.
78/// All requests of one response are executed together or not at all.
79#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
80#[serde(deny_unknown_fields)]
81pub struct ReadRequest {
82    /// Model-assigned id used to match the result.
83    pub request_id: ReadRequestId,
84    /// The tool.
85    pub tool: ReadToolKey,
86    /// Arguments matching the tool's input schema, which the registry checks.
87    pub arguments: serde_json::Value,
88}
89
90/// The normalized result of a read request.
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct ReadResult {
93    /// The request answered.
94    pub request_id: ReadRequestId,
95    /// The tool.
96    pub tool: ReadToolKey,
97    /// Output (possibly truncated).
98    pub output: serde_json::Value,
99    /// Whether the output was truncated to `max_result_bytes`.
100    pub truncated: bool,
101    /// Source label shown to the model (e.g. `"case_state"`, `"knowledge_base"`).
102    pub source_label: String,
103    /// Trust level of the content.
104    pub trust: TrustLevel,
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn read_request_denies_unknown_fields() {
113        let ok = serde_json::json!({
114            "request_id": "r1",
115            "tool": "case.get",
116            "arguments": {"case_id": "inv-1"}
117        });
118        let parsed = serde_json::from_value::<ReadRequest>(ok).expect("a well-formed request");
119        assert_eq!(parsed.arguments["case_id"], "inv-1");
120        let bad = serde_json::json!({
121            "request_id": "r1",
122            "tool": "case.get",
123            "arguments": {},
124            "write": true
125        });
126        assert!(serde_json::from_value::<ReadRequest>(bad).is_err());
127    }
128
129    #[test]
130    fn trust_ordering() {
131        assert!(TrustLevel::Untrusted < TrustLevel::Authoritative);
132        assert!(DataSensitivity::Public < DataSensitivity::Restricted);
133    }
134}