merge-request: finalize one guarded merge outcome

This commit is contained in:
2026-08-16 21:03:30 +09:00
parent 1479148f84
commit 5ea2792df7
9 changed files with 846 additions and 1519 deletions
@@ -12,7 +12,6 @@ pub const MERGE_REQUEST_COMMON_TOOL_NAMES: &[&str] = &[
"MergeRequestReadinessCheck",
"MergeRequestOpen",
"MergeRequestAddRevision",
"MergeRequestRecordMergeResult",
"MergeRequestComplete",
];
pub const MERGE_REQUEST_REVIEW_TOOL_NAME: &str = "MergeRequestReviewSubmit";
@@ -22,7 +21,6 @@ enum Kind {
Readiness,
Open,
AddRevision,
RecordMergeResult,
Complete,
Review,
}
@@ -63,10 +61,10 @@ struct AddRevisionInput {
summary: String,
}
#[derive(Debug, Deserialize, JsonSchema)]
struct RecordMergeResultInput {
struct CompleteInput {
ticket: String,
expected_current_revision_id: String,
operation_id: String,
expected_revision_id: String,
target_commit: String,
source_commit: String,
result_commit: String,
@@ -87,12 +85,6 @@ enum MergeResolutionInput {
ConflictsResolved,
}
#[derive(Debug, Deserialize, JsonSchema)]
struct CompleteInput {
ticket: String,
operation_id: String,
expected_revision_id: String,
}
#[derive(Debug, Deserialize, JsonSchema)]
struct ReviewInput {
decision: ReviewDecisionInput,
#[serde(default)]
@@ -125,7 +117,6 @@ impl Kind {
Self::Readiness => "MergeRequestReadinessCheck",
Self::Open => "MergeRequestOpen",
Self::AddRevision => "MergeRequestAddRevision",
Self::RecordMergeResult => "MergeRequestRecordMergeResult",
Self::Complete => "MergeRequestComplete",
Self::Review => "MergeRequestReviewSubmit",
}
@@ -138,7 +129,6 @@ impl Kind {
Self::Show | Self::Readiness => json!(schemars::schema_for!(ShowInput)),
Self::Open => json!(schemars::schema_for!(OpenInput)),
Self::AddRevision => json!(schemars::schema_for!(AddRevisionInput)),
Self::RecordMergeResult => json!(schemars::schema_for!(RecordMergeResultInput)),
Self::Complete => json!(schemars::schema_for!(CompleteInput)),
Self::Review => json!(schemars::schema_for!(ReviewInput)),
}
@@ -202,8 +192,8 @@ impl Tool for MergeRequestTool {
),
)
}
Kind::RecordMergeResult => {
let v: RecordMergeResultInput = parse(input)?;
Kind::Complete => {
let v: CompleteInput = parse(input)?;
nonempty(&v.ticket)?;
let strategy = match v.strategy {
MergeStrategyInput::FastForward => "fast_forward",
@@ -214,20 +204,6 @@ impl Tool for MergeRequestTool {
MergeResolutionInput::Clean => "clean",
MergeResolutionInput::ConflictsResolved => "conflicts_resolved",
};
(
WorkspaceRequestMethod::Post,
format!(
"/api/w/{workspace_id}/tickets/{}/merge-request/merge-results",
v.ticket
),
Some(
json!({"expected_current_revision_id":v.expected_current_revision_id,"operation_id":v.operation_id,"target_commit":v.target_commit,"source_commit":v.source_commit,"result_commit":v.result_commit,"strategy":strategy,"resolution":resolution}),
),
)
}
Kind::Complete => {
let v: CompleteInput = parse(input)?;
nonempty(&v.ticket)?;
(
WorkspaceRequestMethod::Post,
format!(
@@ -235,7 +211,7 @@ impl Tool for MergeRequestTool {
v.ticket
),
Some(
json!({"operation_id":v.operation_id,"expected_revision_id":v.expected_revision_id}),
json!({"operation_id":v.operation_id,"expected_revision_id":v.expected_revision_id,"target_commit":v.target_commit,"source_commit":v.source_commit,"result_commit":v.result_commit,"strategy":strategy,"resolution":resolution}),
),
)
}
@@ -310,7 +286,6 @@ pub fn common_tools(client: Arc<dyn WorkspaceClient>) -> Vec<ToolDefinition> {
definition(client.clone(), Kind::Readiness),
definition(client.clone(), Kind::Open),
definition(client.clone(), Kind::AddRevision),
definition(client.clone(), Kind::RecordMergeResult),
definition(client, Kind::Complete),
]
}
@@ -338,9 +313,6 @@ pub fn description(name: &str) -> Option<&'static str> {
"MergeRequestAddRevision" => {
Some("Append an immutable revision; prior approval cannot carry to the new revision.")
}
"MergeRequestRecordMergeResult" => Some(
"Record validated immutable integration evidence for the current source revision and target tip.",
),
"MergeRequestComplete" => {
Some("CAS-complete an approved revision with operation-id replay and crash fencing.")
}
@@ -356,14 +328,14 @@ mod tests {
use super::*;
#[test]
fn merge_request_tool_contract_omits_tree_hashes_and_exposes_merge_result() {
fn merge_request_tool_contract_omits_tree_hashes_and_candidate_result_tool() {
let open = serde_json::to_string(&schemars::schema_for!(OpenInput)).unwrap();
let add = serde_json::to_string(&schemars::schema_for!(AddRevisionInput)).unwrap();
let result = serde_json::to_string(&schemars::schema_for!(RecordMergeResultInput)).unwrap();
let complete = serde_json::to_string(&schemars::schema_for!(CompleteInput)).unwrap();
assert!(!open.contains("head_tree"));
assert!(!add.contains("head_tree"));
assert!(result.contains("fast_forward"));
assert!(result.contains("conflicts_resolved"));
assert!(MERGE_REQUEST_COMMON_TOOL_NAMES.contains(&"MergeRequestRecordMergeResult"));
assert!(complete.contains("result_commit"));
assert!(complete.contains("conflicts_resolved"));
assert!(!MERGE_REQUEST_COMMON_TOOL_NAMES.contains(&"MergeRequestRecordMergeResult"));
}
}
+2 -12
View File
@@ -68,9 +68,6 @@ struct SubWorkerSpawnInput {
struct ReviewerHandoffInput {
ticket_id: String,
revision_id: String,
/// Optional immutable MergeResult subject. Omit for source-revision review.
#[serde(default)]
merge_result_id: Option<String>,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
@@ -425,7 +422,6 @@ impl Tool for SubWorkerSpawnTool {
(
review.ticket_id.clone(),
review.revision_id.clone(),
review.merge_result_id.clone(),
uuid::Uuid::now_v7().to_string(),
format!(
"{}{}",
@@ -435,9 +431,7 @@ impl Tool for SubWorkerSpawnTool {
)
});
let child_workspace_context =
if let Some((ticket_id, revision_id, merge_result_id, _, capability_token)) =
&reviewer_attempt
{
if let Some((ticket_id, revision_id, _, capability_token)) = &reviewer_attempt {
let workspace_id =
self.workspace_context
.workspace_id()
@@ -459,7 +453,6 @@ impl Tool for SubWorkerSpawnTool {
ReviewerAttemptContext {
ticket_id: ticket_id.clone(),
revision_id: revision_id.clone(),
merge_result_id: merge_result_id.clone(),
},
capability_token.clone(),
));
@@ -554,9 +547,7 @@ impl Tool for SubWorkerSpawnTool {
}
};
if let Some((ticket_id, revision_id, merge_result_id, attempt_id, capability_token)) =
&reviewer_attempt
{
if let Some((ticket_id, revision_id, attempt_id, capability_token)) = &reviewer_attempt {
let workspace_id = self.workspace_context.workspace_id().ok_or_else(|| {
ToolError::ExecutionFailed("reviewer attempt lost Workspace identity".to_string())
})?;
@@ -588,7 +579,6 @@ impl Tool for SubWorkerSpawnTool {
let body = serde_json::json!({
"attempt_id": attempt_id,
"revision_id": revision_id,
"merge_result_id": merge_result_id,
"child_session_id": child_session_id,
"capability_token": capability_token,
});
-10
View File
@@ -249,7 +249,6 @@ pub trait WorkspaceClient: std::fmt::Debug + Send + Sync {
pub struct ReviewerAttemptContext {
pub ticket_id: String,
pub revision_id: String,
pub merge_result_id: Option<String>,
}
#[derive(Debug)]
@@ -311,14 +310,6 @@ impl WorkspaceClient for ReviewerChildWorkspaceClient {
"revision_id".to_string(),
serde_json::Value::String(self.context.revision_id.clone()),
);
object.insert(
"merge_result_id".to_string(),
self.context
.merge_result_id
.clone()
.map(serde_json::Value::String)
.unwrap_or(serde_json::Value::Null),
);
object.insert(
"capability_token".to_string(),
serde_json::Value::String(self.capability_token.clone()),
@@ -462,7 +453,6 @@ mod reviewer_client_tests {
ReviewerAttemptContext {
ticket_id: "T1".into(),
revision_id: "V1".into(),
merge_result_id: None,
},
"secret".into(),
);