merge-request: add target and merge result authority
This commit is contained in:
@@ -12,6 +12,7 @@ pub const MERGE_REQUEST_COMMON_TOOL_NAMES: &[&str] = &[
|
||||
"MergeRequestReadinessCheck",
|
||||
"MergeRequestOpen",
|
||||
"MergeRequestAddRevision",
|
||||
"MergeRequestRecordMergeResult",
|
||||
"MergeRequestComplete",
|
||||
];
|
||||
pub const MERGE_REQUEST_REVIEW_TOOL_NAME: &str = "MergeRequestReviewSubmit";
|
||||
@@ -21,6 +22,7 @@ enum Kind {
|
||||
Readiness,
|
||||
Open,
|
||||
AddRevision,
|
||||
RecordMergeResult,
|
||||
Complete,
|
||||
Review,
|
||||
}
|
||||
@@ -41,7 +43,6 @@ struct OpenInput {
|
||||
revision_id: String,
|
||||
base_commit: String,
|
||||
head_commit: String,
|
||||
head_tree: String,
|
||||
diff_digest: String,
|
||||
#[serde(default)]
|
||||
changed_paths: Vec<String>,
|
||||
@@ -55,7 +56,6 @@ struct AddRevisionInput {
|
||||
revision_id: String,
|
||||
base_commit: String,
|
||||
head_commit: String,
|
||||
head_tree: String,
|
||||
diff_digest: String,
|
||||
#[serde(default)]
|
||||
changed_paths: Vec<String>,
|
||||
@@ -63,6 +63,30 @@ struct AddRevisionInput {
|
||||
summary: String,
|
||||
}
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct RecordMergeResultInput {
|
||||
ticket: String,
|
||||
expected_current_revision_id: String,
|
||||
operation_id: String,
|
||||
target_commit: String,
|
||||
source_commit: String,
|
||||
result_commit: String,
|
||||
strategy: MergeStrategyInput,
|
||||
resolution: MergeResolutionInput,
|
||||
}
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum MergeStrategyInput {
|
||||
FastForward,
|
||||
Merge,
|
||||
}
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum MergeResolutionInput {
|
||||
None,
|
||||
Clean,
|
||||
ConflictsResolved,
|
||||
}
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct CompleteInput {
|
||||
ticket: String,
|
||||
operation_id: String,
|
||||
@@ -101,6 +125,7 @@ impl Kind {
|
||||
Self::Readiness => "MergeRequestReadinessCheck",
|
||||
Self::Open => "MergeRequestOpen",
|
||||
Self::AddRevision => "MergeRequestAddRevision",
|
||||
Self::RecordMergeResult => "MergeRequestRecordMergeResult",
|
||||
Self::Complete => "MergeRequestComplete",
|
||||
Self::Review => "MergeRequestReviewSubmit",
|
||||
}
|
||||
@@ -113,6 +138,7 @@ 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)),
|
||||
}
|
||||
@@ -158,7 +184,7 @@ impl Tool for MergeRequestTool {
|
||||
WorkspaceRequestMethod::Post,
|
||||
format!("/api/w/{workspace_id}/tickets/{}/merge-request", v.ticket),
|
||||
Some(
|
||||
json!({"repository_id":v.repository_id,"revision_id":v.revision_id,"base_commit":v.base_commit,"head_commit":v.head_commit,"head_tree":v.head_tree,"diff_digest":v.diff_digest,"changed_paths":v.changed_paths,"summary":v.summary}),
|
||||
json!({"repository_id":v.repository_id,"revision_id":v.revision_id,"base_commit":v.base_commit,"head_commit":v.head_commit,"diff_digest":v.diff_digest,"changed_paths":v.changed_paths,"summary":v.summary}),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -172,7 +198,30 @@ impl Tool for MergeRequestTool {
|
||||
v.ticket
|
||||
),
|
||||
Some(
|
||||
json!({"expected_current_revision_id":v.expected_current_revision_id,"revision_id":v.revision_id,"base_commit":v.base_commit,"head_commit":v.head_commit,"head_tree":v.head_tree,"diff_digest":v.diff_digest,"changed_paths":v.changed_paths,"summary":v.summary}),
|
||||
json!({"expected_current_revision_id":v.expected_current_revision_id,"revision_id":v.revision_id,"base_commit":v.base_commit,"head_commit":v.head_commit,"diff_digest":v.diff_digest,"changed_paths":v.changed_paths,"summary":v.summary}),
|
||||
),
|
||||
)
|
||||
}
|
||||
Kind::RecordMergeResult => {
|
||||
let v: RecordMergeResultInput = parse(input)?;
|
||||
nonempty(&v.ticket)?;
|
||||
let strategy = match v.strategy {
|
||||
MergeStrategyInput::FastForward => "fast_forward",
|
||||
MergeStrategyInput::Merge => "merge",
|
||||
};
|
||||
let resolution = match v.resolution {
|
||||
MergeResolutionInput::None => "none",
|
||||
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}),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -261,6 +310,7 @@ 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),
|
||||
]
|
||||
}
|
||||
@@ -288,6 +338,9 @@ 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.")
|
||||
}
|
||||
@@ -297,3 +350,20 @@ pub fn description(name: &str) -> Option<&'static str> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn merge_request_tool_contract_omits_tree_hashes_and_exposes_merge_result() {
|
||||
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();
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ 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)]
|
||||
@@ -422,6 +425,7 @@ impl Tool for SubWorkerSpawnTool {
|
||||
(
|
||||
review.ticket_id.clone(),
|
||||
review.revision_id.clone(),
|
||||
review.merge_result_id.clone(),
|
||||
uuid::Uuid::now_v7().to_string(),
|
||||
format!(
|
||||
"{}{}",
|
||||
@@ -431,7 +435,9 @@ impl Tool for SubWorkerSpawnTool {
|
||||
)
|
||||
});
|
||||
let child_workspace_context =
|
||||
if let Some((ticket_id, revision_id, _, capability_token)) = &reviewer_attempt {
|
||||
if let Some((ticket_id, revision_id, merge_result_id, _, capability_token)) =
|
||||
&reviewer_attempt
|
||||
{
|
||||
let workspace_id =
|
||||
self.workspace_context
|
||||
.workspace_id()
|
||||
@@ -453,6 +459,7 @@ 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(),
|
||||
));
|
||||
@@ -547,7 +554,9 @@ impl Tool for SubWorkerSpawnTool {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((ticket_id, revision_id, attempt_id, capability_token)) = &reviewer_attempt {
|
||||
if let Some((ticket_id, revision_id, merge_result_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())
|
||||
})?;
|
||||
@@ -579,6 +588,7 @@ 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,
|
||||
});
|
||||
|
||||
@@ -249,6 +249,7 @@ 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)]
|
||||
@@ -310,6 +311,14 @@ 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()),
|
||||
@@ -453,6 +462,7 @@ mod reviewer_client_tests {
|
||||
ReviewerAttemptContext {
|
||||
ticket_id: "T1".into(),
|
||||
revision_id: "V1".into(),
|
||||
merge_result_id: None,
|
||||
},
|
||||
"secret".into(),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user