chore: connect shared file contracts for Bash validation

This commit is contained in:
2026-10-20 03:03:53 +09:00
26 changed files with 2586 additions and 275 deletions
+6 -1
View File
@@ -48,6 +48,10 @@ pub struct CheckoutRequest {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "operation", rename_all = "snake_case", deny_unknown_fields)]
pub enum CheckoutOperation {
ReadBytes {
offset: u64,
max_bytes: usize,
},
Read {
offset: usize,
limit: usize,
@@ -71,7 +75,7 @@ pub enum CheckoutOperation {
impl CheckoutOperation {
pub fn capability(&self) -> WorkdirSessionCapability {
match self {
Self::Read { .. } => WorkdirSessionCapability::Read,
Self::Read { .. } | Self::ReadBytes { .. } => WorkdirSessionCapability::Read,
Self::Edit { .. } => WorkdirSessionCapability::Edit,
Self::Write { .. } | Self::Create { .. } => WorkdirSessionCapability::Write,
}
@@ -86,6 +90,7 @@ impl CheckoutOperation {
)]
pub enum CheckoutOutput {
Read(ReadResult),
ReadBytes(crate::ReadBytesResult),
Write(WriteResult),
Edit(EditResult),
}
+12
View File
@@ -186,6 +186,11 @@ fn validate_external_operation(operation: &WorkdirSessionOperation) -> Result<()
&& !request.validator.is_empty()
&& request.validator.len() <= 256
&& match &request.operation {
crate::CheckoutOperation::ReadBytes { offset, max_bytes } => {
*offset <= 9_007_199_254_740_991
&& *max_bytes > 0
&& *max_bytes <= BoundedReadLimits::EXTERNAL_DEFAULT.max_response_bytes
}
crate::CheckoutOperation::Read {
limit, max_bytes, ..
} => {
@@ -375,6 +380,13 @@ fn validate_external_result(result: &WorkdirSessionOperationResult) -> Result<()
WorkdirSessionOperationResult::CheckoutExecute(result) => {
valid_checkout_observation(&result.observation)
&& match &result.output {
crate::CheckoutOutput::ReadBytes(read) => {
read.path == result.observation.path
&& validate_external_result(&WorkdirSessionOperationResult::ReadBytes(
read.clone(),
))
.is_ok()
}
crate::CheckoutOutput::Read(read) => {
read.path == result.observation.path
&& validate_external_result(&WorkdirSessionOperationResult::Read(
+26 -1
View File
@@ -693,6 +693,12 @@ mod client {
let mutation = request.operation.capability() != crate::WorkdirSessionCapability::Read;
let expected_path = request.target.clone();
let expected_output = request.operation.capability();
let byte_range = match &request.operation {
crate::CheckoutOperation::ReadBytes { offset, max_bytes } => {
Some((*offset, *max_bytes))
}
_ => None,
};
match self
.operate(WorkdirSessionOperation::CheckoutExecute(request))
.await
@@ -702,11 +708,30 @@ mod client {
&& !result.observation.validator.is_empty()
&& result.observation.validator.len() <= 256
&& match (&result.output, expected_output) {
(
crate::CheckoutOutput::ReadBytes(read),
crate::WorkdirSessionCapability::Read,
) => byte_range.is_some_and(|(offset, max)| {
read.path == expected_path
&& read.offset == offset
&& read.bytes.len() <= max
&& read
.offset
.checked_add(read.bytes.len() as u64)
.is_some_and(|end| {
end <= read.total_bytes
|| (read.bytes.is_empty() && read.eof)
})
&& read.eof
== (read.offset.saturating_add(read.bytes.len() as u64)
>= read.total_bytes)
}),
(
crate::CheckoutOutput::Read(read),
crate::WorkdirSessionCapability::Read,
) => {
read.path == expected_path
byte_range.is_none()
&& read.path == expected_path
&& read.bytes.len()
<= crate::BoundedReadLimits::EXTERNAL_DEFAULT
.max_response_bytes
+15 -1
View File
@@ -1550,6 +1550,20 @@ impl WorkdirSession for LocalWorkdirSession {
}
let is_create = matches!(&request.operation, Op::Create { .. });
let output = match request.operation {
Op::ReadBytes { offset, max_bytes } => fs_operation::run_read_bytes_bounded(
&self.inner.root,
ReadBytesRequest {
path: path.clone(),
offset,
max_bytes,
expected_hash: None,
},
&pinned,
self.inner
.read_limits
.unwrap_or(BoundedReadLimits::EXTERNAL_DEFAULT),
)
.map(Output::ReadBytes),
Op::Read {
offset,
limit,
@@ -1642,7 +1656,7 @@ impl WorkdirSession for LocalWorkdirSession {
checkout_execute_error(&path, WorkdirError::from(error))
}
})?;
if matches!(&output, Output::Read(_)) {
if matches!(&output, Output::Read(_) | Output::ReadBytes(_)) {
pinned.confirm().map_err(|e| checkout_io(&path, e))?;
return Ok(crate::CheckoutResult {
observation: self
+3
View File
@@ -1260,6 +1260,9 @@ impl WorkdirSession for ScopedWorkdirSession {
.intersection(WorkdirSessionCapabilities::READ_ONLY);
}
if let crate::CheckoutOutput::Read(read) = &mut result.output {
read.path = result_path.clone();
}
if let crate::CheckoutOutput::ReadBytes(read) = &mut result.output {
read.path = result_path;
}
Ok(result)