mod fold;
pub use fold::fold_conversation_trace;
use crate::delegation_history::HandoffPhase;
use polyc_projection::trace_vocabulary::{
MAX_DOCUMENT_BYTES, MAX_HASH_BYTES, MAX_ID_BYTES, MAX_LIST_ITEM_BYTES, MAX_LIST_ITEMS,
MAX_TEXT_BYTES, MAX_WARNING_BYTES,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
pub struct Id(String);
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Text {
value: String,
truncated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ToolDocument {
value: String,
truncated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum TraceBoundsError {
#[error("`{field}` is longer than an identifier may be")]
Identifier {
field: &'static str,
},
#[error("an item of `{field}` is longer than a list item may be")]
ListItem {
field: &'static str,
},
#[error("`{field}` is longer than a hash may be")]
Hash {
field: &'static str,
},
}
impl Id {
pub fn new(field: &'static str, value: &str) -> Result<Self, TraceBoundsError> {
if value.len() > MAX_ID_BYTES {
return Err(TraceBoundsError::Identifier { field });
}
Ok(Self(value.to_owned()))
}
pub fn optional(field: &'static str, value: Option<&str>) -> Result<Self, TraceBoundsError> {
value.map_or_else(|| Ok(Self::default()), |value| Self::new(field, value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl Text {
#[must_use]
pub fn new(value: &str) -> Self {
let (value, truncated) = truncate_at_char_boundary(value, MAX_TEXT_BYTES);
Self { value, truncated }
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.value
}
#[must_use]
pub const fn truncated(&self) -> bool {
self.truncated
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.value.is_empty()
}
}
impl ToolDocument {
#[must_use]
pub fn new(value: &str) -> Self {
let (value, truncated) = truncate_at_char_boundary(value, MAX_DOCUMENT_BYTES);
Self { value, truncated }
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.value
}
#[must_use]
pub const fn truncated(&self) -> bool {
self.truncated
}
}
pub fn bounded_hash(field: &'static str, value: &str) -> Result<Id, TraceBoundsError> {
if value.len() > MAX_HASH_BYTES {
return Err(TraceBoundsError::Hash { field });
}
Ok(Id(value.to_owned()))
}
pub fn bounded_id_list(
field: &'static str,
values: impl IntoIterator<Item = String>,
) -> Result<(Vec<Id>, bool), TraceBoundsError> {
let mut out = Vec::new();
let mut clipped = false;
for value in values {
if out.len() >= MAX_LIST_ITEMS {
clipped = true;
break;
}
if value.len() > MAX_LIST_ITEM_BYTES {
return Err(TraceBoundsError::ListItem { field });
}
out.push(Id(value));
}
Ok((out, clipped))
}
fn truncate_at_char_boundary(value: &str, limit: usize) -> (String, bool) {
if value.len() <= limit {
return (value.to_owned(), false);
}
let mut end = limit;
while end > 0 && !value.is_char_boundary(end) {
end -= 1;
}
(value[..end].to_owned(), true)
}
#[must_use]
pub fn bounded_warning(value: &str) -> String {
truncate_at_char_boundary(value, MAX_WARNING_BYTES).0
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoundaryMarker {
Start,
Dispatched,
Complete,
Failed,
Ambiguous,
StepCommit,
TextWithheld,
IngressDirective,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AudienceView {
pub recorded: Id,
pub source: Id,
pub edge_asserted: Id,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageRole {
Input,
Output,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolStatus {
Denied,
WaitingApproval,
WaitingQuestion,
Completed,
MissingResult,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApprovalPhase {
Request,
Response,
Deferred,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApprovalDecision {
Pending,
Approved,
Denied,
Deferred,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuestionPhase {
Request,
Response,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QuestionDecision {
Pending,
Answered,
Declined,
AutoResolved,
Other(Id),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SubagentPhase {
Spawn,
Result,
ModelCall,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PaymentDirection {
Inbound,
Outbound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RoleVerdict {
Verified,
Invalid,
Untrusted,
LegacyUnverifiable,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MarkerKind {
IncognitoSet,
MultipartyFloor,
AdmissionRefused,
ToolInputRewrite,
ToolContextInjection,
ToolResultRedaction,
Caller,
Participant,
Enrollment,
RoutineGrant,
GrantRevocation,
GrantSuspension,
NudgeSent,
TaintExcision,
ConversationNamespace,
NamespaceRefused,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TurnStatus {
Failed,
Quarantined,
OrphanedDispatch,
WaitingApproval,
WaitingQuestion,
Complete,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FailureKind {
TurnFailed,
Quarantined,
OrphanedDispatch,
WaitingApproval,
WaitingQuestion,
DeniedApproval,
SubagentFailed,
MissingResult,
PaymentAttemptUnsettled,
WalletLinkNeeded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WarningCode {
DecodeFailed,
SignatureUnverified,
UnknownKind,
ListClipped,
UnresolvedPayment,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TraceStepPayload {
Boundary {
marker: BoundaryMarker,
audience: Option<AudienceView>,
source_turn_id: Option<Id>,
failed_kind: Option<Id>,
failed_message: Option<Text>,
},
Message {
role: MessageRole,
text: Text,
tool_call_ids: Vec<Id>,
internal_only: bool,
},
ToolCall {
tool_call_id: Id,
name: Id,
arguments: ToolDocument,
status: ToolStatus,
origin_turn_id: Id,
result_turn_id: Option<Id>,
internal_only: bool,
},
ToolResult {
tool_call_id: Id,
name: Id,
result: ToolDocument,
first_party: bool,
needs_wallet_link: bool,
internal_only: bool,
},
Approval {
phase: ApprovalPhase,
request_id: Id,
tool_name: Option<Id>,
args: Option<ToolDocument>,
decision: ApprovalDecision,
reason: Option<Text>,
signature: Option<RoleVerdict>,
},
Question {
phase: QuestionPhase,
call_id: Id,
index: u32,
header: Option<Text>,
question: Option<Text>,
args: Option<ToolDocument>,
decision: QuestionDecision,
selected_index: Option<u32>,
selected_label: Option<Text>,
answered_by: Option<Id>,
verdict: Option<RoleVerdict>,
},
Subagent {
phase: SubagentPhase,
sub_agent_id: Id,
target_agent_id: Id,
task: Option<Text>,
provider: Option<Id>,
model: Option<Id>,
succeeded: Option<bool>,
error: Option<Text>,
input_tokens: Option<u64>,
output_tokens: Option<u64>,
first_party: Option<bool>,
verdict: Option<RoleVerdict>,
},
Handoff {
phase: HandoffPhase,
child_conversation_id: Option<Id>,
child_agent_id: Id,
carried_count: u32,
reason: Text,
parent_agent_id: Option<Id>,
denial_reason: Option<Text>,
allowed: Vec<Id>,
verdict: RoleVerdict,
},
PaymentAttempt {
approval_pos: Option<u64>,
tool_call_id: Id,
approved_args_hash: Id,
url: Text,
currency: Id,
challenge_amount: Text,
challenge_id: Id,
},
PaymentReceipt {
direction: PaymentDirection,
reference: Id,
amount: Text,
currency: Id,
recipient: Text,
method: Id,
tool_call_id: Id,
approval_pos: Option<u64>,
approved_args_hash: Id,
subject: Id,
kind: Id,
payer_kind: Id,
paying_account: Id,
},
PaymentRefusal {
reason: Id,
reason_detail: Text,
merchant_host: Text,
requested_base_units: u64,
permitted_base_units: u64,
tool_call_id: Id,
subject: Id,
timestamp: u64,
},
WalletLink {
transition: Id,
subject: Id,
wallet_address: Id,
currency: Id,
chain_id: u64,
limit_base_units: u64,
limit_human: Text,
period_secs: u64,
expiry_unix: u64,
recipients: Vec<Id>,
conversation_id: Id,
timestamp: u64,
},
GrantReplay {
tool: Id,
grant_ref: Id,
covered_capabilities: Vec<Id>,
coverage_hash: Id,
turn_id: Option<Id>,
verdict: RoleVerdict,
},
Identity {
provider: Id,
scope: Id,
external_id: Id,
display_name: Text,
persona_id: Id,
},
Summary {
text: Text,
covers_through_position: u64,
},
SummaryGate {
rejected: bool,
dropped_identifiers: Vec<Id>,
count: u64,
},
Marker {
marker: MarkerKind,
on: Option<bool>,
escalated_at_ms: Option<u64>,
},
Excised {
marker_position: Option<u64>,
},
Unknown,
}
impl TraceStepPayload {
#[must_use]
pub const fn step_kind(&self) -> &'static str {
match self {
Self::Boundary { .. } => "boundary",
Self::Message { .. } => "message",
Self::ToolCall { .. } => "tool_call",
Self::ToolResult { .. } => "tool_result",
Self::Approval { .. } => "approval",
Self::Question { .. } => "question",
Self::Subagent { .. } => "subagent",
Self::Handoff { .. } => "handoff",
Self::PaymentAttempt { .. } => "payment_attempt",
Self::PaymentReceipt { .. } => "payment_receipt",
Self::PaymentRefusal { .. } => "payment_refusal",
Self::WalletLink { .. } => "wallet_link",
Self::GrantReplay { .. } => "grant_replay",
Self::Identity { .. } => "identity",
Self::Summary { .. } => "summary",
Self::SummaryGate { .. } => "summary_gate",
Self::Marker { .. } => "marker",
Self::Excised { .. } => "excised",
Self::Unknown => "unknown",
}
}
#[must_use]
pub const fn is_spine_only(&self) -> bool {
matches!(self, Self::Excised { .. } | Self::Unknown)
}
#[must_use]
pub const fn internal_only(&self) -> bool {
match self {
Self::Message { internal_only, .. }
| Self::ToolCall { internal_only, .. }
| Self::ToolResult { internal_only, .. } => *internal_only,
_ => false,
}
}
#[must_use]
pub fn truncated(&self) -> bool {
match self {
Self::Message { text, .. } | Self::Summary { text, .. } => text.truncated(),
Self::ToolCall { arguments, .. } => arguments.truncated(),
Self::ToolResult { result, .. } => result.truncated(),
Self::Approval { args, reason, .. } => {
args.as_ref().is_some_and(ToolDocument::truncated)
|| reason.as_ref().is_some_and(Text::truncated)
}
Self::Question {
header,
question,
args,
selected_label,
..
} => {
header.as_ref().is_some_and(Text::truncated)
|| question.as_ref().is_some_and(Text::truncated)
|| args.as_ref().is_some_and(ToolDocument::truncated)
|| selected_label.as_ref().is_some_and(Text::truncated)
}
Self::Subagent { task, error, .. } => {
task.as_ref().is_some_and(Text::truncated)
|| error.as_ref().is_some_and(Text::truncated)
}
Self::Handoff { reason, .. } => reason.truncated(),
Self::Boundary { failed_message, .. } => {
failed_message.as_ref().is_some_and(Text::truncated)
}
Self::PaymentReceipt {
amount, recipient, ..
} => amount.truncated() || recipient.truncated(),
Self::PaymentRefusal {
reason_detail,
merchant_host,
..
} => reason_detail.truncated() || merchant_host.truncated(),
Self::WalletLink { limit_human, .. } => limit_human.truncated(),
Self::PaymentAttempt { .. }
| Self::SummaryGate { .. }
| Self::Identity { .. }
| Self::GrantReplay { .. }
| Self::Marker { .. }
| Self::Excised { .. }
| Self::Unknown => false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceStepFact {
pub position: u64,
pub ordinal: u64,
pub turn_id: Id,
pub kind_base: Id,
pub trust: Id,
pub payload: TraceStepPayload,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[allow(
clippy::struct_excessive_bools,
reason = "each boolean is one marker the writer either wrote or did not"
)]
pub struct TraceTurnFact {
pub turn_id: Id,
pub first_position: u64,
pub status: TurnStatus,
pub has_start: bool,
pub has_dispatched: bool,
pub has_complete: bool,
pub has_failed: bool,
pub has_ambiguous: bool,
pub failed_kind: Id,
pub failed_message: Text,
pub failed_position: Option<u64>,
pub model_provider: Id,
pub model_name: Id,
pub system_config_ref: Id,
pub temperature_micros: Option<u64>,
pub top_p_micros: Option<u64>,
pub max_tokens: Option<u64>,
pub reasoning_level: Id,
pub dispatch_clock_ms: Option<u64>,
pub usage_input: Option<u64>,
pub usage_output: Option<u64>,
pub caller_persona_id: Id,
pub plannable: bool,
pub evidence_positions: Vec<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceFailureFact {
pub turn_id: Id,
pub rank: u32,
pub kind: FailureKind,
pub message: Text,
pub evidence_positions: Vec<u64>,
pub related_ids: Vec<Id>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceWarningFact {
pub position: Option<u64>,
pub code: WarningCode,
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ConversationTraceFacts {
pub signers: Vec<TraceSignerFact>,
pub steps: Vec<TraceStepFact>,
pub turns: Vec<TraceTurnFact>,
pub failures: Vec<TraceFailureFact>,
pub warnings: Vec<TraceWarningFact>,
}
#[derive(Debug, Clone)]
pub struct TraceTrust {
pub approval: Vec<Vec<u8>>,
pub handoff: polyc_crypto::signing_role::RoleTrustSet<polyc_crypto::signing_role::HandoffRole>,
pub subagent:
polyc_crypto::signing_role::RoleTrustSet<polyc_crypto::signing_role::SubagentRole>,
pub question: Vec<Vec<u8>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TraceSignerFact {
pub position: u64,
pub ordinal: u64,
pub step_kind: &'static str,
pub role: SignerRole,
pub signer_key: [u8; 32],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignerRole {
Approval,
Question,
GrantReplay,
Payment,
WalletLink,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ConversationTraceError {
#[error("the source prefix repeats position {position}")]
RepeatedPosition {
position: u64,
},
#[error("position {position}: {source}")]
Bounds {
position: u64,
source: TraceBoundsError,
},
#[error("position {position} produced more steps than the bound allows")]
OrdinalOverflow {
position: u64,
},
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs, clippy::unwrap_used)]
use super::*;
use polyc_projection::trace_vocabulary::STEP_KINDS;
use std::collections::BTreeSet;
fn every_payload() -> Vec<TraceStepPayload> {
vec![
TraceStepPayload::Boundary {
marker: BoundaryMarker::Start,
audience: None,
source_turn_id: None,
failed_kind: None,
failed_message: None,
},
TraceStepPayload::Message {
role: MessageRole::Input,
text: Text::new("hello"),
tool_call_ids: Vec::new(),
internal_only: false,
},
TraceStepPayload::ToolCall {
tool_call_id: Id::new("tool_call_id", "call-1").unwrap(),
name: Id::new("name", "read_file").unwrap(),
arguments: ToolDocument::new("{}"),
status: ToolStatus::Completed,
origin_turn_id: Id::new("origin_turn_id", "turn-1").unwrap(),
result_turn_id: None,
internal_only: false,
},
TraceStepPayload::ToolResult {
tool_call_id: Id::new("tool_call_id", "call-1").unwrap(),
name: Id::new("name", "read_file").unwrap(),
result: ToolDocument::new("{}"),
first_party: true,
needs_wallet_link: false,
internal_only: false,
},
TraceStepPayload::Approval {
phase: ApprovalPhase::Request,
request_id: Id::new("request_id", "call-1").unwrap(),
tool_name: None,
args: None,
decision: ApprovalDecision::Pending,
reason: None,
signature: None,
},
TraceStepPayload::Question {
phase: QuestionPhase::Request,
call_id: Id::new("call_id", "call-1").unwrap(),
index: 0,
header: None,
question: None,
args: None,
decision: QuestionDecision::Pending,
selected_index: None,
selected_label: None,
answered_by: None,
verdict: None,
},
TraceStepPayload::Subagent {
phase: SubagentPhase::Spawn,
sub_agent_id: Id::new("sub_agent_id", "sub-1").unwrap(),
target_agent_id: Id::new("target_agent_id", "researcher").unwrap(),
task: None,
provider: None,
model: None,
succeeded: None,
error: None,
input_tokens: None,
output_tokens: None,
first_party: None,
verdict: None,
},
TraceStepPayload::Handoff {
phase: HandoffPhase::Handoff,
child_conversation_id: None,
child_agent_id: Id::new("child_agent_id", "researcher").unwrap(),
carried_count: 0,
reason: Text::new("delegate"),
parent_agent_id: None,
denial_reason: None,
allowed: Vec::new(),
verdict: RoleVerdict::Verified,
},
TraceStepPayload::PaymentAttempt {
approval_pos: None,
tool_call_id: Id::new("tool_call_id", "call-1").unwrap(),
approved_args_hash: Id::default(),
url: Text::new("https://example.test"),
currency: Id::new("currency", "USD").unwrap(),
challenge_amount: Text::new("1.00"),
challenge_id: Id::new("challenge_id", "ch-1").unwrap(),
},
TraceStepPayload::PaymentReceipt {
direction: PaymentDirection::Outbound,
reference: Id::new("reference", "ref-1").unwrap(),
amount: Text::new("1.00"),
currency: Id::new("currency", "USD").unwrap(),
recipient: Text::new("merchant"),
method: Id::new("method", "card").unwrap(),
tool_call_id: Id::new("tool_call_id", "call-1").unwrap(),
approval_pos: None,
approved_args_hash: Id::default(),
subject: Id::new("subject", "persona-1").unwrap(),
kind: Id::new("kind", "settled").unwrap(),
payer_kind: Id::new("payer_kind", "persona").unwrap(),
paying_account: Id::new("paying_account", "acct-1").unwrap(),
},
TraceStepPayload::PaymentRefusal {
reason: Id::new("reason", "over_limit").unwrap(),
reason_detail: Text::new("above the period cap"),
merchant_host: Text::new("example.test"),
requested_base_units: 200,
permitted_base_units: 100,
tool_call_id: Id::new("tool_call_id", "call-1").unwrap(),
subject: Id::new("subject", "persona-1").unwrap(),
timestamp: 1,
},
TraceStepPayload::WalletLink {
transition: Id::new("transition", "linked").unwrap(),
subject: Id::new("subject", "persona-1").unwrap(),
wallet_address: Id::new("wallet_address", "0xabc").unwrap(),
currency: Id::new("currency", "USD").unwrap(),
chain_id: 1,
limit_base_units: 100,
limit_human: Text::new("1.00"),
period_secs: 86_400,
expiry_unix: 0,
recipients: Vec::new(),
conversation_id: Id::new("conversation_id", "conv-1").unwrap(),
timestamp: 1,
},
TraceStepPayload::GrantReplay {
tool: Id::new("tool", "paid_fetch").unwrap(),
grant_ref: Id::new("grant_ref", "grant-1").unwrap(),
covered_capabilities: Vec::new(),
coverage_hash: Id::default(),
turn_id: None,
verdict: RoleVerdict::Verified,
},
TraceStepPayload::Identity {
provider: Id::new("provider", "chat").unwrap(),
scope: Id::new("scope", "team-a").unwrap(),
external_id: Id::new("external_id", "U-1").unwrap(),
display_name: Text::new("Ada"),
persona_id: Id::new("persona_id", "persona-1").unwrap(),
},
TraceStepPayload::Summary {
text: Text::new("so far"),
covers_through_position: 9,
},
TraceStepPayload::SummaryGate {
rejected: true,
dropped_identifiers: Vec::new(),
count: 1,
},
TraceStepPayload::Marker {
marker: MarkerKind::IncognitoSet,
on: Some(true),
escalated_at_ms: None,
},
TraceStepPayload::Excised {
marker_position: Some(4),
},
TraceStepPayload::Unknown,
]
}
#[test]
fn every_step_kind_label_has_exactly_one_variant() {
let produced: BTreeSet<&str> = every_payload()
.iter()
.map(TraceStepPayload::step_kind)
.collect();
let declared: BTreeSet<&str> = STEP_KINDS.iter().copied().collect();
assert_eq!(
produced, declared,
"the payload variants and the closed label set disagree"
);
assert_eq!(
every_payload().len(),
STEP_KINDS.len(),
"one variant per label, no variant sharing a label"
);
}
#[test]
fn only_excised_and_unknown_are_spine_only() {
for payload in every_payload() {
let expected = matches!(
payload,
TraceStepPayload::Excised { .. } | TraceStepPayload::Unknown
);
assert_eq!(
payload.is_spine_only(),
expected,
"`{}` disagrees about carrying a payload table",
payload.step_kind()
);
}
}
#[test]
fn an_over_long_identifier_is_refused_rather_than_cut() {
let long = "x".repeat(MAX_ID_BYTES + 1);
assert_eq!(
Id::new("turn_id", &long),
Err(TraceBoundsError::Identifier { field: "turn_id" })
);
let exact = "x".repeat(MAX_ID_BYTES);
assert_eq!(Id::new("turn_id", &exact).unwrap().as_str(), exact);
}
#[test]
fn over_long_text_is_cut_on_a_character_boundary_and_flagged() {
let text = Text::new(&"é".repeat(MAX_TEXT_BYTES));
assert!(text.truncated(), "the text was over its bound");
assert!(
text.as_str().len() <= MAX_TEXT_BYTES,
"the cut respects the bound"
);
assert!(
std::str::from_utf8(text.as_str().as_bytes()).is_ok(),
"the cut leaves valid UTF-8"
);
let short = Text::new("fits");
assert!(!short.truncated());
assert_eq!(short.as_str(), "fits");
}
#[test]
fn a_list_clips_by_count_and_refuses_by_item() {
let many = (0..MAX_LIST_ITEMS + 5).map(|i| i.to_string());
let (items, clipped) = bounded_id_list("tool_call_ids", many).unwrap();
assert_eq!(items.len(), MAX_LIST_ITEMS);
assert!(clipped, "a clipped list reports that it was clipped");
let over_item = vec!["x".repeat(MAX_LIST_ITEM_BYTES + 1)];
assert_eq!(
bounded_id_list("tool_call_ids", over_item),
Err(TraceBoundsError::ListItem {
field: "tool_call_ids"
})
);
}
#[test]
fn an_item_past_the_count_bound_is_never_inspected() {
let mut values: Vec<String> = (0..MAX_LIST_ITEMS).map(|i| i.to_string()).collect();
values.push("x".repeat(MAX_LIST_ITEM_BYTES + 1));
let (items, clipped) = bounded_id_list("allowed", values).unwrap();
assert_eq!(items.len(), MAX_LIST_ITEMS);
assert!(clipped);
}
#[test]
fn internal_only_belongs_to_the_payloads_a_message_carries() {
for payload in every_payload() {
let can_be_internal = matches!(
payload,
TraceStepPayload::Message { .. }
| TraceStepPayload::ToolCall { .. }
| TraceStepPayload::ToolResult { .. }
);
if !can_be_internal {
assert!(
!payload.internal_only(),
"`{}` must never claim to be internal-only",
payload.step_kind()
);
}
}
}
}