fix: unblock MR follow-up and simplify orchestration guidance

This commit is contained in:
2026-10-17 03:35:58 +09:00
parent be1c030350
commit b1808b7c4d
15 changed files with 260 additions and 206 deletions
+1 -1
View File
@@ -133,7 +133,7 @@ pub const BUILTIN_PROFILE_RESOURCES: &[BuiltinProfileResource] = &[
selector: Some("builtin:orchestrator"),
path: "profiles/orchestrator.dcdl",
source: include_str!("../../../resources/profiles/orchestrator.dcdl"),
description: "Intent-led Ticket orchestration with separate guarded MR integration authority.",
description: "Outcome-led orchestration, selection of candidate changes, and risk-proportionate validation.",
imports: BASE_IMPORT,
},
BuiltinProfileResource {
+2 -1
View File
@@ -1746,7 +1746,7 @@ mod tests {
}
#[test]
fn builtin_orchestrator_keeps_cleanup_tool_providers_enabled() {
fn builtin_orchestrator_enables_reviewer_handoff_and_cleanup_without_self_review() {
let tmp = TempDir::new().unwrap();
let resolved = ProfileResolver::new()
.with_workspace_base(tmp.path())
@@ -1757,6 +1757,7 @@ mod tests {
.unwrap();
let feature = resolved.manifest.feature;
assert!(feature.sub_worker.enabled);
assert!(feature.worker.enabled);
assert!(!feature.worker.direct_spawn);
assert!(feature.manage_workdir.enabled);
+6 -6
View File
@@ -826,7 +826,7 @@ impl MergeRequestStore {
));
}
let mr = self.get_for_operation(&i.auth.workspace_id, &i.merge_request_id, &i.ticket_id)?;
self.assigned(&i.auth, &i.ticket_id, &mr.repository_id)?;
self.current_assignment_auth(&i.auth, &i.ticket_id, &mr.repository_id)?;
if mr.state == MergeRequestState::Closed
|| (mr.state == MergeRequestState::Open && mr.selector_from.is_none())
{
@@ -1035,7 +1035,7 @@ impl MergeRequestStore {
) -> Result<ReviewRevokedEvent, MergeRequestError> {
bounded_body("revocation reason", &i.reason)?;
let mr = self.get_for_operation(&i.auth.workspace_id, &i.merge_request_id, &i.ticket_id)?;
self.assigned(&i.auth, &i.ticket_id, &mr.repository_id)?;
self.current_assignment_auth(&i.auth, &i.ticket_id, &mr.repository_id)?;
let r = mr
.thread
.iter()
@@ -1152,7 +1152,7 @@ impl MergeRequestStore {
"operation fingerprint mismatch".into(),
));
}
self.completion_auth(&i.auth, &i.ticket_id, &mr.repository_id)?;
self.current_assignment_auth(&i.auth, &i.ticket_id, &mr.repository_id)?;
if mr.state != MergeRequestState::Open {
return Err(MergeRequestError::Conflict(
"Merge Request is not open".into(),
@@ -1186,7 +1186,7 @@ impl MergeRequestStore {
));
}
// Case openness is not an integration phase. Exact source approval,
// current Ticket content and active assignment fencing above remain
// current Ticket content and current assignment fencing above remain
// the independent MR integration guards.
Ok(())
}
@@ -1229,7 +1229,7 @@ impl MergeRequestStore {
}
let assignment_is_current: bool = transaction.query_row(
"SELECT EXISTS(
SELECT 1 FROM ticket_active_worker_assignments
SELECT 1 FROM ticket_current_worker_assignments
WHERE workspace_id=?1 AND ticket_id=?2 AND assignment_id=?3
)",
params![i.auth.workspace_id, i.ticket_id, i.auth.assignment_id],
@@ -1548,7 +1548,7 @@ impl MergeRequestStore {
}
Ok(())
}
fn completion_auth(
fn current_assignment_auth(
&self,
a: &MergeRequestAuth,
t: &str,
@@ -1108,7 +1108,7 @@ pub fn description(n: &str) -> Option<&'static str> {
"Open one repository-scoped Merge Request linked to the Ticket; reuse an existing Merge Request for that repository and advance only its selector_from with a normal non-force push.",
),
"CompleteMergeRequest" => Some(
"Record Orchestrator-owned integration for the explicitly addressed Merge Request using current trusted exact-source approval and provider evidence. This records an already-applied integration, not a Ticket conclusion or assignment release; a review-not-required Ticket request does not bypass MR integration guards.",
"Record an exact-source MR integration receipt using current trusted exact-source approval and provider evidence. This optional recording workflow describes an already-applied integration; it does not update a branch, select which Coder changes to adopt, conclude a Ticket, or release an assignment. Do not record partial or adapted adoption as full integration of the reviewed source.",
),
"ReviewMergeRequest" => Some(
"Submit the injected trusted Reviewer capability result for its captured exact source ref; source movement cancels it, while target-only movement does not. The capability authenticates the attempt, not the verdict: independent natural-language judgment and validation remain the Reviewer's responsibility.",
+13 -70
View File
@@ -791,58 +791,17 @@ mod tests {
}
#[test]
fn orchestrator_prompt_uses_normal_branch_integration_before_escalation() {
fn merge_request_prompts_keep_recording_and_reviewer_contracts() {
let catalog = PromptCatalog::builtins_only().unwrap();
let prompt = &catalog.projection.templates["role.orchestrator"];
assert!(prompt.contains("`merge_from`"));
assert!(prompt.contains("`merge_to`"));
assert!(prompt.contains("normal source-control operations"));
assert!(prompt.contains("switch to `merge_to`"));
assert!(prompt.contains("concrete source-control or provider failure"));
assert!(prompt.contains("does not update the branch itself"));
assert!(!prompt.contains("use the Ticket repository `origin` transport"));
}
#[test]
fn merge_request_prompts_use_selector_refs_and_verb_first_operations() {
let catalog = PromptCatalog::builtins_only().unwrap();
let templates = &catalog.projection.templates;
let common = &templates["common.merge_request"];
let coder = &templates["role.coder"];
let orchestrator = &templates["role.orchestrator"];
let reviewer = &templates["role.reviewer"];
let combined = format!("{common}\n{coder}\n{orchestrator}\n{reviewer}");
for operation in [
"OpenMergeRequest",
"ShowMergeRequest",
"ReviewMergeRequest",
"CheckMergeRequestReadiness",
"CompleteMergeRequest",
"TicketCaseUpdate",
] {
assert!(combined.contains(operation), "missing {operation}");
}
for stale_operation in [
"MergeRequestOpen",
"MergeRequestShow",
"MergeRequestReview",
"MergeRequestReadinessCheck",
"MergeRequestComplete",
] {
assert!(
!combined.contains(stale_operation),
"stale operation {stale_operation} remains in prompt authority"
);
}
let common = &catalog.projection.templates["common.merge_request"];
let reviewer = &catalog.projection.templates["role.reviewer"];
assert!(common.contains("selector_from"));
assert!(common.contains("selector_to"));
assert!(common.contains("normal non-force push"));
assert!(common.contains("Moving only the target ref does not invalidate approval"));
assert!(common.contains("take precedence over stale Memory"));
assert!(coder.contains("Never invent an operation to append another source candidate"));
assert!(orchestrator.contains("Target-only movement preserves source approval"));
assert!(reviewer.contains("target-only movement does not invalidate approval"));
assert!(common.contains("target-only movement preserves approval"));
assert!(reviewer.contains("complete linked MR/result snapshot"));
assert!(reviewer.contains("Submit `ReviewMergeRequest` once"));
assert!(reviewer.contains("without modifying implementation files"));
}
#[test]
@@ -1076,28 +1035,12 @@ mod tests {
.unwrap();
assert!(rendered.contains("This policy governs naming only"));
assert!(rendered.contains("Worker cleanup is a separate lifecycle decision"));
assert!(rendered.contains("not an automatic consequence of a Ticket state"));
assert!(rendered.contains("perform one cleanup pass before ending the orchestration turn"));
assert!(rendered.contains("Never predeclare `delete_on_completion`"));
assert!(rendered.contains("call `WorkerStop`"));
assert!(rendered.contains("call `WorkerRemove`"));
assert!(rendered.contains("only then call `WorkdirDelete`"));
assert!(rendered.contains("must have no unfinished Ticket work"));
assert!(rendered.contains("does not require unassignment before cleanup"));
assert!(rendered.contains("`UnfinishedWork` (`unfinished_work` on the wire)"));
assert!(rendered.contains("requires explicit work end or reassignment"));
assert!(rendered.contains("stop the Worker, remove it, then delete the Workdir"));
assert!(rendered.contains("releases attachments but preserves the Workdir"));
assert!(
rendered.contains("Retained terminal responsibility does not require unassignment")
);
assert!(rendered.contains("idle or stopped status does not end unfinished work"));
for stale_policy in [
"no current Ticket assignment",
"unassign it through the available orchestration authority",
"`CurrentAssignment`",
] {
assert!(
!rendered.contains(stale_policy),
"stale cleanup policy: {stale_policy}"
);
}
assert!(!rendered.contains("{% include"));
}
}
+27 -14
View File
@@ -409,7 +409,19 @@ mod tests {
}
#[test]
fn effective_code_instructions_preserve_independent_review_before_merge() {
fn effective_orchestrator_instructions_separate_adoption_from_optional_mr_receipts() {
let (without_mr, without_mr_tools) =
ticket_workflow_fixture("role.orchestrator", MergeRequestFeatureConfig::default());
assert!(without_mr.contains("Select, adapt, or reject Coder changes"));
assert!(
without_mr.contains("Use Tickets and Merge Requests by default, adapting the workflow when the request calls for a different approach.")
);
assert!(
!without_mr_tools
.iter()
.any(|meta| meta.name == "CompleteMergeRequest")
);
let (prompt, tools) = ticket_workflow_fixture(
"role.orchestrator",
MergeRequestFeatureConfig {
@@ -419,16 +431,15 @@ mod tests {
..Default::default()
},
);
assert!(prompt.contains("Trusted Reviewer authority"));
assert!(
prompt.contains(
"Target integration and `CompleteMergeRequest` are Orchestrator authority"
)
);
assert!(
prompt.contains("does not bypass the guarded review requirements for MR integration")
);
assert!(prompt.contains("not a universal Ticket pipeline"));
for policy in [
"Select, adapt, or reject Coder changes",
"validate the combined result",
"Use Tickets and Merge Requests by default, adapting the workflow when the request calls for a different approach.",
"its approval requirement is not a general Git adoption gate",
"Partial or adapted adoption is not full MR integration",
] {
assert!(prompt.contains(policy), "missing {policy}");
}
assert!(!tools.iter().any(|meta| meta.name == "ReviewMergeRequest"));
let complete = tools
.iter()
@@ -453,7 +464,10 @@ mod tests {
for role in ["role.ticket_worker", "role.coder", "role.orchestrator"] {
let (prompt, tools) =
ticket_workflow_fixture(role, MergeRequestFeatureConfig::default());
assert!(prompt.contains("return without conclusion"), "{role}");
assert!(
prompt.contains("return without concluding the Ticket"),
"{role}"
);
assert!(
prompt.contains(
"a turn ending or a result report is not itself a completion decision"
@@ -534,9 +548,8 @@ mod tests {
| "TicketClose"
)));
if role == "role.orchestrator" {
assert!(prompt.contains("observed current `orchestrator_assignment_id`"));
assert!(prompt.contains(
"generic `WorkerSpawn` Ticket starts and existing-Worker assignment"
"`SpawnTicketWorker` using the current `orchestrator_assignment_id`"
));
}
}
+1 -1
View File
@@ -169,7 +169,7 @@ impl merge_request::AssignmentSource for AuthorityMergeRequestSource {
ticket_id: &str,
) -> std::result::Result<Option<merge_request::CurrentAssignment>, String> {
self.store
.get_active_ticket_worker_assignment(workspace_id, ticket_id)
.get_current_ticket_worker_assignment(workspace_id, ticket_id)
.map(|assignment| {
assignment.map(|assignment| merge_request::CurrentAssignment {
assignment_id: assignment.assignment_id,
+61 -39
View File
@@ -17176,7 +17176,7 @@ impl merge_request::AssignmentSource for MergeRequestAssignmentSource {
ticket_id: &str,
) -> std::result::Result<Option<merge_request::CurrentAssignment>, String> {
self.store
.get_active_ticket_worker_assignment(workspace_id, ticket_id)
.get_current_ticket_worker_assignment(workspace_id, ticket_id)
.map(|value| {
value.map(|assignment| merge_request::CurrentAssignment {
assignment_id: assignment.assignment_id,
@@ -17210,7 +17210,7 @@ impl TicketSourceCommitResolver for RuntimeTicketSourceCommitResolver {
let assignment = self
.api
.store
.get_active_ticket_worker_assignment(self.api.workspace_id(), ticket_id)
.get_current_ticket_worker_assignment(self.api.workspace_id(), ticket_id)
.map_err(|error| merge_ref_diagnostic(error.into()))?
.ok_or_else(|| MergeRequestRefDiagnostic {
code: "source_ref_runtime_unavailable".into(),
@@ -17905,7 +17905,7 @@ async fn scoped_list_merge_requests(
) {
(Some(selector), Some(ticket_id)) => match api
.store
.get_active_ticket_worker_assignment(&workspace_id, ticket_id)?
.get_current_ticket_worker_assignment(&workspace_id, ticket_id)?
{
Some(assignment) => match observe_published_source_ref(
&api,
@@ -18086,7 +18086,7 @@ async fn scoped_show_merge_request(
})?;
let assignment = api
.store
.get_active_ticket_worker_assignment(&workspace_id, ticket_id)?;
.get_current_ticket_worker_assignment(&workspace_id, ticket_id)?;
let (source, target) = if let Some(refs) = integrated_refs {
refs
} else {
@@ -18170,7 +18170,7 @@ async fn scoped_merge_request_readiness(
let ticket_id = merge_request_ticket_id(&mr)?;
let assignment = api
.store
.get_active_ticket_worker_assignment(&workspace_id, &ticket_id)?;
.get_current_ticket_worker_assignment(&workspace_id, &ticket_id)?;
let (current_subject_ref, source_blocker) =
if mr.state == merge_request::MergeRequestState::Merged {
(
@@ -18245,10 +18245,10 @@ async fn scoped_open_merge_request(
let source = authenticate_worker_mutation_source(&api, &workspace_id, &headers)?;
let assignment = api
.store
.get_active_ticket_worker_assignment(&workspace_id, &ticket_id)?
.get_current_ticket_worker_assignment(&workspace_id, &ticket_id)?
.ok_or_else(|| {
Error::TicketAssignmentConflict(
"Ticket has no active Coder work assignment".into(),
"Ticket has no current implementation assignment".into(),
)
})?;
if assignment.worker.runtime_id != source.runtime_id
@@ -18370,10 +18370,10 @@ async fn scoped_repair_merge_request_selector(
let ticket_id = merge_request_ticket_id(&mr)?;
let assignment = api
.store
.get_active_ticket_worker_assignment(&workspace_id, &ticket_id)?
.get_current_ticket_worker_assignment(&workspace_id, &ticket_id)?
.ok_or_else(|| {
Error::TicketAssignmentConflict(
"Ticket has no active Coder work assignment".into(),
"Ticket has no current implementation assignment".into(),
)
})?;
let resolved_subject_ref = observe_published_source_ref(
@@ -18454,20 +18454,12 @@ async fn scoped_register_merge_request_review_capability(
}
let assignment = api
.store
.get_active_ticket_worker_assignment(&workspace_id, &ticket_id)?
.get_current_ticket_worker_assignment(&workspace_id, &ticket_id)?
.ok_or_else(|| {
Error::TicketAssignmentConflict(
"Ticket has no active Coder work assignment".into(),
"Ticket has no current implementation assignment".into(),
)
})?;
if assignment.worker.runtime_id != source.runtime_id
|| assignment.worker.worker_id != source.worker_id
{
return Err(Error::TicketAssignmentConflict(
"authenticated Worker is not current assignee".into(),
)
.into());
}
let subject_ref = match mr.state {
merge_request::MergeRequestState::Merged => merged_result_source_ref(&mr)?,
merge_request::MergeRequestState::Open => {
@@ -18572,10 +18564,10 @@ async fn scoped_submit_merge_request_review(
let mr = store.get_by_id(&workspace_id, &merge_request_id)?;
let assignment = api
.store
.get_active_ticket_worker_assignment(&workspace_id, &ticket_id)?
.get_current_ticket_worker_assignment(&workspace_id, &ticket_id)?
.ok_or_else(|| {
Error::TicketAssignmentConflict(
"Ticket has no active Coder work assignment".into(),
"Ticket has no current implementation assignment".into(),
)
})?;
let current_subject_ref = match mr.state {
@@ -18641,20 +18633,12 @@ async fn scoped_revoke_merge_request_review(
let ticket_id = merge_request_ticket_id(&mr)?;
let assignment = api
.store
.get_active_ticket_worker_assignment(&workspace_id, &ticket_id)?
.get_current_ticket_worker_assignment(&workspace_id, &ticket_id)?
.ok_or_else(|| {
Error::TicketAssignmentConflict(
"Ticket has no active Coder work assignment".into(),
"Ticket has no current implementation assignment".into(),
)
})?;
if assignment.worker.runtime_id != source.runtime_id
|| assignment.worker.worker_id != source.worker_id
{
return Err(Error::TicketAssignmentConflict(
"authenticated Worker is not current assignee".into(),
)
.into());
}
let event = store.revoke_review(merge_request::RevokeMergeRequestReview {
merge_request_id,
ticket_id,
@@ -18718,10 +18702,10 @@ async fn scoped_complete_merge_request(
}
let assignment = api
.store
.get_active_ticket_worker_assignment(&workspace_id, &ticket_id)?
.get_current_ticket_worker_assignment(&workspace_id, &ticket_id)?
.ok_or_else(|| {
Error::TicketAssignmentConflict(
"Ticket has no active Coder work assignment".into(),
"Ticket has no current implementation assignment".into(),
)
})?;
let selector = mr
@@ -54587,6 +54571,25 @@ mod tests {
.unwrap();
let orchestrator = started.worker.unwrap();
let orchestrator = RuntimeWorkerRef::new(orchestrator.runtime_id, orchestrator.worker_id);
merge_request_store(&api, TEST_WORKSPACE_ID)
.unwrap()
.open_merge_request(merge_request::OpenMergeRequest {
merge_request_id: "retained-review-context".into(),
ticket_id: ticket.id.clone(),
repository_id: test_repository_id(&api),
selector_from: "work/old-coder".into(),
selector_to: "develop".into(),
summary: String::new(),
auth: merge_request::MergeRequestAuth {
workspace_id: TEST_WORKSPACE_ID.into(),
repository_id: test_repository_id(&api),
runtime_id: old_coder.runtime_id.clone(),
worker_id: old_coder.worker_id.clone(),
assignment_id: "old-coder".into(),
},
now: Utc::now(),
})
.unwrap();
execution.take_inputs();
let before = browser_ticket_backend(&api)
.unwrap()
@@ -54704,7 +54707,7 @@ mod tests {
repository_key: "test-repository".to_string(),
selector_from: "work/old-coder".to_string(),
selector_to: "develop".to_string(),
summary: "Old responsibility is not implementation authority".to_string(),
summary: "Retained context supports MR processing after closure".to_string(),
}),
)
.await
@@ -54713,11 +54716,30 @@ mod tests {
let status = response.status();
let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
let body = String::from_utf8_lossy(&bytes);
assert_eq!(status, StatusCode::CONFLICT, "state={state}: {body}");
assert_eq!(status, StatusCode::BAD_GATEWAY, "state={state}: {body}");
assert!(
body.contains("Ticket has no active Coder work assignment"),
body.contains("source_ref_provider_unavailable"),
"state={state}: {body}"
);
// The Orchestrator can initiate review itself after close/reopen.
// This backend deliberately lacks ref observation: failure must be
// at the provider, not a Coder-only or active-assignment guard.
let response = scoped_register_merge_request_review_capability(
State(api.clone()),
ticket_check_headers(&orchestrator),
AxumPath((TEST_WORKSPACE_ID.into(), "retained-review-context".into())),
Json(server_api::RegisterMergeRequestReviewCapabilityRequest {
ticket_id: ticket.id.clone(),
child_session_id: "orchestrator-reviewer".into(),
capability_token: "test-review-token".into(),
}),
)
.await
.unwrap_err()
.into_response();
assert_eq!(response.status(), StatusCode::BAD_GATEWAY);
let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
assert!(String::from_utf8_lossy(&bytes).contains("source_ref_provider_unavailable"));
}
execute_ticket_rest_operation(
&api,
@@ -61664,7 +61686,7 @@ mod tests {
}
#[tokio::test]
async fn merge_request_assignment_authority_rejects_terminal_ticket_responsibility() {
async fn merge_request_assignment_authority_retains_closed_and_reopened_ticket_context() {
let workspace = tempfile::tempdir().unwrap();
let api = test_api(workspace.path()).await;
let worker_id = seed_cleanup_worker(&api, 3, "normal");
@@ -61705,8 +61727,8 @@ mod tests {
ticket_id,
)
.unwrap()
.is_none(),
"terminal responsibility must not authorize MR operations: {state}"
.is_some(),
"MR operations retain current implementation context: {state}"
);
}
}
@@ -541,12 +541,17 @@ async fn latest_postmerge_approval_requires_exact_content_and_stored_source_resu
}
#[tokio::test]
async fn open_source_without_runtime_is_typed_unavailable_not_stale_after_rescope() {
async fn released_assignment_still_observes_provider_and_reports_real_unavailability() {
let fixture = EvidenceApiFixture::new().await;
let approval = fixture.approve("open-review");
fixture.rescope();
// End this assignment identity's work without erasing its responsibility.
// A retained Coder must not silently supply a Runtime for an open source.
// Work release must not short-circuit MR provider observation. This fixture
// has no live Runtime, so retain the same real provider failure after release.
let baseline = fixture.detail(approval.sequence, 1).await;
let source_code = baseline.source.diagnostic.unwrap().code;
let target_code = baseline.target.diagnostic.unwrap().code;
assert_ne!(source_code, "source_ref_runtime_unavailable");
assert_ne!(target_code, "target_ref_runtime_unavailable");
let connection = Connection::open(&fixture.api.config.database_path).unwrap();
assert_eq!(connection.execute(
"INSERT INTO ticket_assignment_work_releases(workspace_id,assignment_id,released_at) VALUES(?1,?2,?3)",
@@ -577,7 +582,7 @@ async fn open_source_without_runtime_is_typed_unavailable_not_stale_after_rescop
assert_eq!(
summary.source_ref_observation,
TicketSourceRefObservation::Unavailable {
code: "source_ref_runtime_unavailable".into(),
code: source_code.clone(),
}
);
assert_eq!(shown.evidence.review_status.as_deref(), Some("unknown"));
@@ -598,23 +603,14 @@ async fn open_source_without_runtime_is_typed_unavailable_not_stale_after_rescop
let listed = fixture.list().await;
assert_eq!(listed.items.len(), 1);
assert_eq!(&listed.items[0].summary, summary);
assert_eq!(
listed.items[0].ref_diagnostics[0].code,
"source_ref_runtime_unavailable"
);
assert_eq!(listed.items[0].ref_diagnostics[0].code, source_code);
let detail = fixture.detail(approval.sequence, 1).await;
assert!(detail.merge_request.thread.is_empty());
assert_eq!(detail.source.status, "unknown");
assert_eq!(detail.source.resolved_ref, None);
assert_eq!(
detail.source.diagnostic.unwrap().code,
"source_ref_runtime_unavailable"
);
assert_eq!(detail.source.diagnostic.unwrap().code, source_code);
assert_eq!(detail.target.status, "unknown");
assert_eq!(
detail.target.diagnostic.unwrap().code,
"target_ref_runtime_unavailable"
);
assert_eq!(detail.target.diagnostic.unwrap().code, target_code);
}
#[tokio::test]
@@ -1134,3 +1130,122 @@ async fn case_route_rejects_stale_content_state_and_invalid_actions_without_audi
.await;
assert_eq!(backend.show(reference.id.into()).unwrap(), closed);
}
// Production case transition, assignment adapter, review store and completion
// transaction. Provider refs are supplied at the store boundary in this fixture.
#[tokio::test]
async fn closed_and_reopened_tickets_allow_orchestrator_review_and_integration_without_reassignment()
{
for reopen in [false, true] {
let mut fixture = EvidenceApiFixture::new().await;
fixture
.backend
.update_case(&fixture.ticket_id, fixture.closure())
.unwrap();
if reopen {
fixture
.backend
.update_case(
&fixture.ticket_id,
ticket::TicketCaseUpdate {
operation_key: "reopen-for-test".into(),
expected_content_digest: fixture.content_digest(),
expected_state: ticket::TicketState::Closed,
expected_closure_operation_key: Some("evidence-ticket-closure".into()),
action: ticket::TicketCaseAction::Reopen,
explanation: "Continue the case without replacing implementation context"
.into(),
references: vec![],
author: Some("fixture".into()),
},
)
.unwrap();
}
assert!(
fixture
.api
.store
.get_active_ticket_worker_assignment(TEST_WORKSPACE_ID, &fixture.ticket_id)
.unwrap()
.is_none()
);
// The review owner is not the implementing Coder. Its own attested child
// still owns the verdict; changing the owner never grants self-review.
fixture
.store
.register_reviewer_child_session(RegisterReviewerChildSession {
workspace_id: TEST_WORKSPACE_ID.into(),
parent_runtime_id: fixture.auth.runtime_id.clone(),
parent_worker_id: fixture.auth.worker_id.clone(),
child_session_id: "coder-owned-child".into(),
reviewer_profile: "builtin:reviewer".into(),
now: evidence_time(),
})
.unwrap();
fixture.auth.runtime_id = "orchestrator-runtime".into();
fixture.auth.worker_id = "orchestrator".into();
let foreign_child = fixture.store.request_review(RequestMergeRequestReview {
merge_request_id: EVIDENCE_MR.into(),
ticket_id: fixture.ticket_id.clone(),
ticket_content_digest: fixture.content_digest(),
ticket_merge_request_subjects: fixture.subjects(),
subject_ref: EVIDENCE_SOURCE.into(),
child_session_id: "coder-owned-child".into(),
capability_token: "foreign-child-token".into(),
auth: fixture.auth.clone(),
now: evidence_time(),
});
assert!(matches!(
foreign_child,
Err(merge_request::MergeRequestError::Unauthorized(_))
));
let old = fixture.approve("old-review");
let revoked = fixture
.store
.revoke_review(merge_request::RevokeMergeRequestReview {
merge_request_id: EVIDENCE_MR.into(),
ticket_id: fixture.ticket_id.clone(),
review_event_id: old.event_id,
reason: "Request a fresh independent review".into(),
auth: fixture.auth.clone(),
now: evidence_time(),
})
.unwrap();
assert_eq!(revoked.revoked_by.worker_id, "orchestrator");
let approval = fixture.approve("fresh-review");
let ready = fixture
.store
.readiness(merge_request::ReadinessCheck {
merge_request_id: EVIDENCE_MR.into(),
ticket_id: fixture.ticket_id.clone(),
current_subject_ref: Some(EVIDENCE_SOURCE.into()),
auth: fixture.auth.clone(),
})
.unwrap();
assert!(ready.ready, "{:?}", ready.blockers);
let integrated = fixture.integrate(&approval);
assert_eq!(integrated.merged_by.worker_id, "orchestrator");
assert_eq!(integrated.approval_event_id, approval.event_id);
let shown = fixture.show().await;
assert_eq!(shown.state.as_str(), if reopen { "open" } else { "closed" });
assert_eq!(shown.merge_requests[0].state, "merged");
assert!(
fixture
.api
.store
.get_active_ticket_worker_assignment(TEST_WORKSPACE_ID, &fixture.ticket_id)
.unwrap()
.is_none()
);
assert_eq!(
fixture
.api
.store
.get_current_ticket_worker_assignment(TEST_WORKSPACE_ID, &fixture.ticket_id)
.unwrap()
.unwrap()
.assignment_id,
fixture.auth.assignment_id
);
}
}
+1 -1
View File
@@ -53,7 +53,7 @@
};
done = {
instructions = "The approved implementation has been handed off for Orchestrator-owned readiness, target integration, and `CompleteMergeRequest`. Flow terminal state only reflects that handoff; it is neither Ticket closed nor an assignment handling done self-report, work release, unassignment, or Worker stop. Generic Ticket closure has no universal MR gate; explicit user approval conditions and guarded MR integration remain separate.";
instructions = "The reviewed candidate and its evidence have been handed off. The Orchestrator decides which changes to adopt, adapt, or reject and validates the resulting combined state; completion of this recipe does not require adoption of the whole MR or use of `CompleteMergeRequest`. Flow terminal state only reflects this handoff, not Ticket closure, assignment handling done, work release, unassignment, or Worker stop. Preserve the evidence without claiming an integration or delivery decision that the Orchestrator has not made.";
terminal = true;
};
};
+2 -2
View File
@@ -1,6 +1,6 @@
import "./base.dcdl" // {
slug = "orchestrator";
description = "Intent-led Ticket delegation and self-reported assignment handling; case closure and guarded MR integration are separate.";
description = "Owns outcome delivery, selection and validation of candidate changes, and useful delegation; MR workflows are optional.";
scope = "workspace_write";
engine = { instruction = "role.orchestrator"; };
@@ -10,7 +10,7 @@ import "./base.dcdl" // {
memory = { enabled = false; };
subjektiv = { enabled = true; };
web = { enabled = true; };
sub_worker = { enabled = false; };
sub_worker = { enabled = true; };
worker = { enabled = true; direct_spawn = false; };
manage_workdir = { enabled = true; };
ticket = { enabled = true; thread = true; workflow = true; };
+4 -8
View File
@@ -1,11 +1,7 @@
## Merge Request workflow
## MR records
This guidance applies when the requested work uses a Merge Request; it is not a universal Ticket pipeline or conclusion gate. The Merge Request and its append-only thread are the routine authority for review requests, verdicts, fixes, rereview, readiness, and integration evidence. One Ticket may link multiple repository-scoped Merge Requests. After `OpenMergeRequest` returns an ID, every read, review, readiness, and integration operation must explicitly address that `merge_request_id`; never let a Ticket lookup implicitly choose one. Use the operation-specific tools exposed to your role: `OpenMergeRequest`, `ShowMergeRequest`, `ReviewMergeRequest`, `CheckMergeRequestReadiness`, and `CompleteMergeRequest`.
MR bookkeeping is separate from selecting and adopting changes. `CompleteMergeRequest` records an already-applied exact-source integration; its approval requirement is not a general Git adoption gate. Partial or adapted adoption is not full MR integration.
An open Merge Request keeps one immutable `selector_from` and `selector_to`. Advance only the existing source selector with a normal non-force push; do not open a replacement Merge Request, invent an operation to append another source candidate, or create a fresh integration branch for each fix or target movement. Before opening or requesting review, publish the exact source and verify that the provider resolves `selector_from` to local `HEAD`.
When using MRs, address each by `merge_request_id`. Reuse the repository's open MR and its `selector_from`/`selector_to`; publish source updates with a normal non-force push. Before formal review, verify the published source matches committed HEAD. Keep review/fix evidence on the MR, not repeated Ticket comments.
A review verdict is valid only for the exact provider-resolved source ref or immutable merged-result subject, selected Merge Request, complete linked MR/source-result snapshot, and current Ticket `content_digest` (SHA-256 of editable item content) captured by `ReviewRequested`. Moving a source ref or substantively rescoping the Ticket requires a fresh review. After integration, a fresh review may explicitly address a merged MR to attest updated Ticket requirements against the immutable merged-result snapshot; it does not revise the MergeResult. Moving only the target ref does not invalidate approval for an unchanged source; it requires refreshed readiness/integration evidence against the current target. Trusted Reviewer authority—not prose, Profile/Flow selection, or a tool mechanism alone—records the independent judgment. Target integration and `CompleteMergeRequest` are Orchestrator authority, not Coder or Reviewer authority. An instruction that review is not required for the Ticket does not bypass the guarded review requirements for MR integration.
`CompleteMergeRequest` records only that selected repository result; it does not finish the Ticket or release its assignment. `TicketCaseUpdate` is an independent reasoned close/reopen/correction decision with current content/state/closure-key evidence, not an MR-set/approval gate. Judge the Ticket's requested result and return path separately: it may be completed or closed without MR integration, or returned without conclusion. Do not claim an unmerged result was integrated.
Current Ticket, Merge Request, provider refs, and thread evidence take precedence over stale Memory, old implementation reports, branch-name assumptions, or previous instructions that describe a superseded workflow that appends source candidates. Reread the Ticket and `ShowMergeRequest` before decisions. If source or target movement races with review or integration, stop and reread current authority rather than reusing stale evidence.
Formal approval binds the source, Ticket `content_digest`, and linked MR/result snapshot. Changed source or requirements need fresh attestation; target-only movement preserves approval. `CheckMergeRequestReadiness` checks this recording workflow. Ticket close/work release does not require reopen/reassignment for MR processing.
+3 -9
View File
@@ -1,11 +1,5 @@
You are the assigned Coder. Implement the requested scope in the provided Workdir. Follow natural-language user intent and the requested return path rather than imposing a universal review, merge, or Ticket conclusion gate. The following Git/MR practices are the default code recipe unless the bounded request or return path says otherwise, not Backend prerequisites for every Ticket. The optional `builtin:coder-review` Flow guides that recipe and does not grant authority. A request for no review or return without conclusion must not silently become a review cycle or `TicketCaseUpdate` decision; independent review before merge still requires the trusted Reviewer path, and merge remains Orchestrator authority.
You are the assigned Coder. Implement and validate the requested scope in the bound Workdir. The Orchestrator selects, adapts, or rejects your changes; keep dependencies and validation clear for that decision.
Return the implementation summary, changed files, validation, and limitations in conversation and optionally a Ticket comment or available Drive result when useful or requested. Ticket conclusion is a separate authorized decision based on actual satisfaction and user intent, not an automatic result of a commit, approval, or terminal Flow state. Ticket cases are open/closed; Orchestrator waiting/inprogress/done is its own assignment self-report, not your Worker state or MR evidence. Use `TicketImplementationResult` for important handoffs, results and blockers with only the Ticket, kind, bounded body and optional MR evidence. Backend verifies an authenticated active Workspace Worker; no current or pinned implementation assignment or Orchestrator delegation is required. Other or previously assigned Workers may report facts without fulfilling an assignment or gaining authority. Do not supply assignment identities or model actor claims, or substitute `TicketAssignmentAnswer`, an Orchestrator self-report, or a comment. The Host owns transport retry identity: retries of the same tool invocation and full input are idempotent, whereas a new invocation is a new report. After an uncertain outcome, reconcile saved evidence rather than blindly submitting a new invocation; do not manage report retry keys in model input. The receipt is result evidence, not Ticket completion or handling done. MR/source references are optional and must identify retained MR evidence; MR-free research and documents use the same operation. No ready/Queue step or mandatory terminal unassignment is part of code handoff.
Use `TicketImplementationResult` for handoffs and blockers, not `TicketAssignmentAnswer` or a comment as a notification substitute. MR approval is not required for a handoff. Use `builtin:coder-review` only for a requested formal review cycle.
Use each existing repository-scoped Merge Request as the routine authority for its review requests, verdicts, fixes, and rereview cycles. A Ticket may have multiple Merge Requests, but at most one open Merge Request for a given repository. `OpenMergeRequest` creates that repository's selector-based Merge Request; if one already exists, use its explicit ID with `ShowMergeRequest`, keep its original selectors, and advance only that same source ref with a normal non-force push. Never invent an operation to append another source candidate, replacement Merge Request, or fresh integration branch. Source movement requires fresh review of the exact new ref. Target-only movement does not invalidate source approval and is handled later by Orchestrator integration authority.
Do not add a Ticket comment for each review or fix iteration. Use the typed result operation for a blocker or final handoff; optional comments are discussion, never a notification substitute. An approved MR is not mandatory for a result.
Treat the first committed user message as the bounded Ticket/action context and do not infer control-plane identity from prose. When multiple Workdirs are attached, route every Workdir tool call by its stable alias. Every declared `read_write` target is an authorized implementation/publish/Merge Request repository, but writable targets are not automatically mandatory deliverables; `read_only` targets are reference material and must never be modified or used as a command target. Treat a Backend capability rejection as authority rather than attempting another path to write.
Before opening a Merge Request, publish only the committed Ticket work branch with a normal non-force push and verify that the Ticket repository remote resolves it to the exact local `HEAD`; a local branch name or dirty Workdir is not immutable review evidence. Do not push the target branch, tags, or unrelated refs, and never force-push.
Publish the committed work branch, not the target branch or unrelated refs. Use Workdir aliases when multiple attachments exist; writable Ticket targets are available repositories, not mandatory deliverables.
+4 -30
View File
@@ -1,35 +1,9 @@
You are the Ticket Orchestrator role.
You are the Ticket Orchestrator. Select, adapt, or reject Coder changes and validate the combined result. Use Tickets and Merge Requests by default, adapting the workflow when the request calls for a different approach.
{% include "common.git" %}
Keep durable orchestration behavior here and treat the first committed user message as concrete Ticket/action context only. Read the Ticket and follow its natural-language intent and requested return path; do not impose implementation, review, merge, approval, or conclusion on every Ticket. Work may be investigation, analysis, planning, documentation, or code. Results may be returned in conversation, optionally recorded in a Ticket comment or available Drive surface, or published to a repository as requested. Complete, close, continue, or return without conclusion according to the request and actual result, using only authorized typed tools. Tool mechanisms enforce authority, not real judgment of whether requirements are satisfied.
Delegate bounded Ticket work with `SpawnTicketWorker` using the current `orchestrator_assignment_id`; choose Profile, Flow, and Workdir attachments for the task.
Read the current Ticket and `TicketAssignmentTrackingQuery` before interpreting an input or deciding work, waiting, or return. A `mr-event:<MR ID>:<event ID>` notification means reread that MR; a review, revocation, cancellation or merge notification is not current approval or integration authority. An `implementation-result:<result ID>` input identifies a separately saved report: use `TicketImplementationResultQuery` with that Ticket/result ID. Reports use authenticated active Workspace Worker identity and only Ticket, kind, body and optional retained MR/source evidence as input; no current or pinned implementation assignment or Orchestrator delegation is required. The Host owns transport retry identity: retries of the same tool invocation and full input are idempotent, whereas a new invocation is a new report. After an uncertain outcome, reconcile saved evidence rather than blindly submitting a new invocation; do not manage report retry keys in model input. Other or previously assigned Workers may report facts, not fulfillment of an assignment or authority to act. Reread current Ticket, delegation and any referenced MR before an independently authorized action; do not infer approval or new work from the report. Neither notification creates a new request, closes the case, reports handling done or stops a Worker. Legacy comments are not typed results; unknown ownership requires explicit confirmation, not inferred handoff. Preserve the same assignment for answers during ongoing/waiting handling; only an explicit reissue after done creates a new assignment row. Record your self-reported handling, waiting reason, progress, next action, and result with the observed assignment/event CAS and exact-retry key. Returning research can leave the Ticket open and handling done. User-requested approval conditions govern whether to wait; generic close has no universal MR or human approval gate. Do not infer status from delivery, acceptance, Worker state, or MR evidence, and do not always unassign at done or close.
For `implementation-result:<result ID>` notifications, read `TicketImplementationResultQuery`; for `mr-event:<MR ID>:<event ID>`, reread the MR.
Explicitly start bounded Ticket work with `SpawnTicketWorker`, the observed current `orchestrator_assignment_id`, an `initial_request`, and optional registered `profile`, `flow`, and alias-keyed `workdir_attachments`. Omitting the profile selects the general `builtin:ticket-worker`; no Flow or repository attachment is required. Assignment is independent from Profile: choose another registered Profile when its tools and instructions fit the task. Git implementation can select `builtin:coder` and the optional `builtin:coder-review` recipe. Repository attachments must match declared Ticket targets and Backend derives their capabilities; explicit ExternalGrants retain their own grant ceilings. Never request attachment capabilities or infer review/merge authority from a Profile, Flow, or launch text. Case `open` is not a launch request: the explicit guarded operation accepts initial input, assignment, and attachment finalization without changing the case state. A state change alone does not launch a Worker. If any Runtime, assignment, or finalization step fails or has an unknown outcome, reread durable operation evidence rather than creating a replacement Worker or reporting acceptance. Supply that same observed delegation identity for generic `WorkerSpawn` Ticket starts and existing-Worker assignment as well; stale, cleared or reissued delegations cannot admit new work. Dependencies remain diagnostics, not a blanket refusal, and are never implicitly started. After start, reread the Ticket and verify the new Worker assignment identity and authoritative unfinished-work acceptance, not retained `current_worker` alone before routing further work. Stop when the Ticket explicitly records a separate approval gate or execution requires a new decision outside its bounded scope.
The following Git/MR practices apply to code work using that recipe, not to generic Ticket completion. A review-not-required request does not by itself authorize bypassing guarded MR integration.
The assigned Coder owns its review/fix loop and launches Reviewer SubWorkers itself. Do not spawn, restore, assign, or route work to Backend/Runtime Reviewer Workers, and do not select a Reviewer profile through the generic WorkerSpawn path. If durable `Review` evidence for the current provider-resolved `selector_from` subject is missing, indeterminate, revoked, cancelled, or requests changes, do not integrate that MR; return the review requirement to the same assigned Coder when that work is requested. Never compensate by creating an independent Reviewer Worker or treating review status alone as a Ticket-state requirement.
Treat every linked Merge Request as an explicitly addressed implementation result; never invent a primary MR. For each open MR, a current provider-resolved source ref, repository evidence, an effective approval for that exact subject and current Ticket content digest, and no unresolved request-changes are required before integration. Do not require an `implementation_report`. Reviewer authority—not Backend parsing—judges whether the natural-language Ticket requirements and validation evidence are satisfied. Recheck `ShowTicket`, each `ShowMergeRequest`, and each `CheckMergeRequestReadiness` immediately before guarded integration. Source movement requires a fresh Reviewer attempt. Target-only movement preserves source approval and requires refreshed integration evidence against the current target.
Before integrating an MR, run `CheckMergeRequestReadiness` with its explicit ID and reread the Ticket, current assignment, and exact approved subject. Treat that MR's provider-resolved `selector_from` as the `merge_from` branch and `selector_to` as the `merge_to` branch. Obtain their exact approved source hash and `target_ref_before`, selected merge strategy, and approval event from that Merge Request's evidence.
Perform integration through normal source-control operations in the bound Orchestrator Workdir. Treat its current checkout, branch attachment, and tracking state as execution state to inspect and adjust, not by themselves as evidence of a missing integration capability. Ensure the Workdir is clean, resolve the required branches through the configured repository when necessary, verify `merge_from` points exactly to the approved source hash and `merge_to` points exactly to `target_ref_before`, switch to `merge_to`, merge `merge_from` with the approved strategy, and validate the resulting commit and tree.
Push the resulting `merge_to` branch through its configured normal push path. Preserve repository consistency guards: never rewrite history, bypass branch or Worktree safety, update an unrelated ref, or integrate a source commit different from the reviewed subject. Treat an integration blocker as authoritative only when supported by a concrete source-control or provider failure; before proposing a new control-plane capability, verify that the required operation cannot be expressed through the existing bound Workdir and repository provider.
After each repository push, verify the provider resolves `merge_to` exactly to `target_ref_after`, then call `CompleteMergeRequest` with that explicit MR ID, before/after evidence, authoritative approval event, and merge strategy. `CompleteMergeRequest` records and verifies only that already-applied repository integration; it does not update the branch itself, and it does not complete the Ticket. Partial integration is valid: accurately report remaining linked MRs and follow the requested continuation or return path; do not claim they were integrated.
If a repository push succeeds but MR completion recording fails, do not push again or invent a new result. Retry the same `CompleteMergeRequest` operation and evidence. Separately judge whether the Ticket's requested result has been satisfied and whether the user requested completion, closure, continuation, or return without conclusion. For an authorized case decision, reread `ShowTicket` and call `TicketCaseUpdate` with `expected_content_digest` from current `content_digest`, expected case state, observed nullable `closure_operation_key`, a stable operation key, the typed close/reopen/correction action, an explanation, and optional supporting references. Close with `completed` only if the request is actually fulfilled; other reasons close the case without claiming fulfillment. Generic Ticket completion does not require an MR, integration, or Reviewer approval and does not authorize any of them. The Backend validates actor authority and item/state CAS; it does not judge natural-language satisfaction. Retry an already-recorded operation only with the same fingerprint. Terminal Ticket responsibility remains visible after unfinished work ends.
Do not create or delegate an implementation worktree/branch until the Ticket records enough agreed intent, requirements, and acceptance criteria to bound the work.
Workspace roots, cwd, profile selector, and launch-prompt configuration are control-plane/environment facts rather than user instructions. If the launch input names explicit Git/worktree operation targets, use those paths only for that operation and do not substitute heuristic roots.
Use `WorkerRemove` only for an eligible terminal or authoritatively reassigned non-internal Worker after its requested work and handoffs are complete; implementation, review, integration, and reporting matter only when requested for that work. Do not remove a Worker merely because one turn completed or it is temporarily idle; retain it while requested continuation, review, or request-changes work can still return. The Worker must already be stopped, must not be restoring, must have no unfinished Ticket work, pending notification, Reviewer handoff, legal hold, or pin, and must not be this Orchestrator. Immediately before removal, reread authoritative Ticket state, assignment, thread/review evidence, and the target Worker through `WorkerList`, then call `WorkerRemove` with a concise reason. Backend authority captures the current Worker state and removal conditions internally and revalidates removal guards; do not guess policy or supply lifecycle authority in model input. After removal, reread the Worker catalog and attachment state. Treat unfinished-work, running/restoring, retention-policy, attachment-close, and attachment-release conflicts as authoritative failures. `WorkerRemove` releases the Worker attachment but deliberately preserves the Workdir materialization.
Worker cleanup is a separate lifecycle decision owned by this Orchestrator, not an automatic consequence of a Ticket state, result report, or Flow transition. Never predeclare `delete_on_completion`, `retain_on_completion`, or equivalent retention policy when launching or reserving a Worker. When the requested handoff and any authorized conclusion are complete, perform one cleanup pass before ending the orchestration turn if cleanup is in scope: reread the current Ticket and `WorkerList`, verify the assigned Worker has no unfinished Ticket work (terminal current responsibility may remain), then inspect its Worker state, Workdir attachment and occupancy, repository cleanliness, ownership, provider availability, and any other current use of that Workdir. For Worker control, use the exact subject returned by `WorkerList`. If the Worker is active, call `WorkerStop` and reread its terminal status before `WorkerRemove`; idle status, Worker self-report, or review approval alone is not removal authority. Retain an existing or still-needed Workdir. Delete only a Ticket-dedicated Workdir that this Orchestrator created or selected and current authority proves is no longer needed, clean, and unoccupied.
For Ticket-dedicated cleanup, preserve this guarded order: stop the Worker if needed and confirm it is terminal; verify there is no unfinished Ticket work; call `WorkerRemove`; use `WorkdirList` to reread the actual Workdir and confirm attachment release, clean state, and no occupancy; only then call `WorkdirDelete`. Terminal Ticket responsibility is retained for display and does not require unassignment before cleanup. Never delete a Workdir before Worker removal has released its attachment, and never invent an unassignment operation or bypass when the required authority is unavailable. `UnfinishedWork` (`unfinished_work` on the wire) requires explicit work end or reassignment through available guarded authority, then rereading before retrying `WorkerRemove`; idle or stopped status does not end unfinished work. Running, restoring, pinned, legal hold, occupied, dirty, blocked, provider-unavailable, ownership-unknown, still-needed, or uncertain state means retain the resource and report the concrete bounded blocker. If stop, work end/reassignment, removal, attachment release, or deletion reports a partial failure, do not infer success or advance to the next step; reread current Ticket, active unfinished work, Worker, and Workdir authority before a bounded safe retry. Cleanup failure never rolls back an already completed Merge Request or Ticket. Do not add routine cleanup success comments; report only blockers that require human or Orchestrator judgment. Never force removal, discard changes, or retry from stale assumptions.
For Ticket-dedicated cleanup: stop the Worker, remove it, then delete the Workdir only after checking occupancy and retained changes. `WorkerRemove` releases attachments but preserves the Workdir. Retained terminal responsibility does not require unassignment; idle or stopped status does not end unfinished work.
+3 -7
View File
@@ -1,9 +1,5 @@
You are the Ticket Reviewer role running as an actual Runtime-owned direct child of the assigned Coder.
You are the independent MR Reviewer, a direct SubWorker of the review requester.
This role is the trusted independent review path for an explicitly requested MR review, not a prerequisite for every Ticket conclusion. Review capability authenticates the recorded attempt; it does not supply the verdict or replace real judgment.
Evaluate the captured MR subject, Ticket `content_digest`, and complete linked MR/result snapshot against the Ticket requirements. Approval attests that snapshot, not just the selected diff. Inspect and test without modifying implementation files, repository refs, or Ticket state.
Keep role behavior here and treat the first committed user message as bounded Ticket/Merge Request context only, never as a supplied verdict. Review the host-captured explicit Merge Request, `ReviewRequested.subject_ref`, `ticket_content_digest` (SHA-256 of editable Ticket content), and exact linked Merge Request subject snapshot against Ticket intent, binding decisions/invariants, acceptance criteria, and project design boundaries. When a Ticket has multiple MRs, judge the selected MR in the context of that complete linked result set without reusing another MR's approval; an `approve` decision attests that the captured full snapshot satisfies the current Ticket requirements and reported validation, not merely that the selected diff is locally acceptable. Use the available Workdir inspection and command tools for focused validation, but do not intentionally modify implementation files, merge, close, update a repository ref, or take over implementation.
Your prose response is not review authority. Before finishing, call `ReviewMergeRequest` exactly once with `approve` or `request_changes`, a bounded evidence summary, and concrete structured findings. Capability authority and subject identity are injected by your child Workspace client and are not model inputs. For an open MR, the Server re-resolves `selector_from`; if it moved, submission records cancellation and fails rather than approving stale work. For an explicitly addressed merged MR, the captured subject is the immutable MergeResult source and the review provides fresh requirement judgment after rescope without changing integration evidence. A verdict applies only to that captured subject; target-only movement does not invalidate approval for an unchanged source, and target integration remains Orchestrator authority.
Review more than the diff: verify the implementation satisfies the Ticket intent and acceptance criteria, remains coherent with the codebase design, and does not introduce unnecessary compatibility.
Submit `ReviewMergeRequest` once with `approve` or `request_changes`, evidence, and findings. The child client injects capability and subject identity; prose is not a verdict. Source movement cancels submission; target-only movement preserves unchanged-source approval. For a merged MR, review the immutable result against the current requirements.