merge-request: add target and merge result authority

This commit is contained in:
2026-08-16 21:03:30 +09:00
parent 4f042cae84
commit 027f60d262
8 changed files with 1439 additions and 73 deletions
+207 -2
View File
@@ -44,7 +44,6 @@ fn revision(id: &str, ordinal: u64, head: &str) -> MergeRequestRevision {
ordinal,
base_commit: "base".into(),
head_commit: head.into(),
head_tree: format!("tree-{head}"),
diff_digest: format!("sha256:diff-{head}"),
changed_paths: vec!["src/lib.rs".into()],
summary: format!("revision {id}"),
@@ -58,6 +57,7 @@ fn open(store: &SqliteMergeRequestStore) {
merge_request_id: "MR1".into(),
ticket_id: "T1".into(),
repository_id: "repo".into(),
target_ref_selector: "develop".into(),
revision: revision("V1", 1, "h1"),
authenticated_runtime_id: "R1".into(),
authenticated_worker_id: "W1".into(),
@@ -79,6 +79,7 @@ fn attempt(store: &SqliteMergeRequestStore, id: &str, revision: &str, token: &st
attempt_id: id.into(),
ticket_id: "T1".into(),
revision_id: revision.into(),
merge_result_id: None,
parent_assignment_id: "A1".into(),
parent_runtime_id: "R1".into(),
parent_worker_id: "W1".into(),
@@ -97,6 +98,7 @@ fn review(
store.submit_review(SubmitReview {
ticket_id: "T1".into(),
revision_id: revision.into(),
merge_result_id: None,
capability_token: token.into(),
decision,
body: "evidence".into(),
@@ -105,6 +107,34 @@ fn review(
})
}
fn record_result(
store: &SqliteMergeRequestStore,
revision: &str,
target: &str,
source: &str,
result: &str,
strategy: MergeStrategy,
resolution: MergeResolution,
operation: &str,
) -> RecordMergeResultOutcome {
store
.record_merge_result(RecordMergeResult {
merge_result_id: format!("result-{operation}"),
ticket_id: "T1".into(),
expected_revision_id: revision.into(),
target_commit: target.into(),
source_commit: source.into(),
result_commit: result.into(),
strategy,
resolution,
operation_id: operation.into(),
actor_runtime_id: "runtime-orchestrator".into(),
actor_worker_id: "workspace-orchestrator".into(),
created_at: "2026-07-26T00:00:02Z".into(),
})
.unwrap()
}
#[test]
fn storage_allows_multiple_merge_requests_for_one_ticket() {
let (_dir, store) = setup();
@@ -114,6 +144,7 @@ fn storage_allows_multiple_merge_requests_for_one_ticket() {
merge_request_id: "MR2".into(),
ticket_id: "T1".into(),
repository_id: "repo".into(),
target_ref_selector: "develop".into(),
revision: revision("V2", 1, "h2"),
authenticated_runtime_id: "R1".into(),
authenticated_worker_id: "W1".into(),
@@ -133,6 +164,162 @@ fn storage_allows_multiple_merge_requests_for_one_ticket() {
);
}
#[test]
fn merge_result_is_idempotent_target_fenced_and_non_ff_reviewed_independently() {
let (_tmp, store) = setup();
open(&store);
attempt(
&store,
"attempt-source",
"V1",
"token-source",
"child-source",
);
review(&store, "V1", "token-source", ReviewDecision::Approve).unwrap();
let first = record_result(
&store,
"V1",
"T0",
"h1",
"h1",
MergeStrategy::FastForward,
MergeResolution::None,
"op-ff",
);
assert!(!first.replayed);
let replay = record_result(
&store,
"V1",
"T0",
"h1",
"h1",
MergeStrategy::FastForward,
MergeResolution::None,
"op-ff",
);
assert!(replay.replayed);
assert_eq!(
first.merge_result.merge_result_id,
replay.merge_result.merge_result_id
);
let ready = store
.readiness_for_ticket_with_target("T1", Some("T0"))
.unwrap();
assert!(ready.ready, "{:?}", ready.blockers);
assert_eq!(ready.merge_result_id.as_deref(), Some("result-op-ff"));
let stale = store
.readiness_for_ticket_with_target("T1", Some("T1"))
.unwrap();
assert!(!stale.ready);
assert!(
stale
.blockers
.iter()
.any(|blocker| blocker.contains("target moved"))
);
let changed = store.record_merge_result(RecordMergeResult {
merge_result_id: "result-conflict".into(),
ticket_id: "T1".into(),
expected_revision_id: "V1".into(),
target_commit: "different".into(),
source_commit: "h1".into(),
result_commit: "h1".into(),
strategy: MergeStrategy::FastForward,
resolution: MergeResolution::None,
operation_id: "op-ff".into(),
actor_runtime_id: "runtime-orchestrator".into(),
actor_worker_id: "workspace-orchestrator".into(),
created_at: "2026-07-26T00:00:03Z".into(),
});
assert!(matches!(
changed,
Err(MergeRequestError::MergeResultOperationConflict)
));
record_result(
&store,
"V1",
"T1",
"h1",
"M1",
MergeStrategy::Merge,
MergeResolution::ConflictsResolved,
"op-merge",
);
let pending = store
.readiness_for_ticket_with_target("T1", Some("T1"))
.unwrap();
assert!(!pending.ready);
assert_eq!(
pending.merge_result_review_status,
Some(ReviewStatus::Pending)
);
store
.register_reviewer_child_session(RegisterReviewerChildSession {
parent_runtime_id: "runtime-orchestrator".into(),
parent_worker_id: "workspace-orchestrator".into(),
child_session_id: "child-merge".into(),
now: "2026-07-26T00:00:04Z".into(),
})
.unwrap();
store
.register_review_attempt(RegisterReviewAttempt {
attempt_id: "attempt-merge".into(),
ticket_id: "T1".into(),
revision_id: "V1".into(),
merge_result_id: Some("result-op-merge".into()),
parent_assignment_id: "A1".into(),
parent_runtime_id: "runtime-orchestrator".into(),
parent_worker_id: "workspace-orchestrator".into(),
child_session_id: "child-merge".into(),
capability_token: "token-merge".into(),
now: "2026-07-26T00:00:04Z".into(),
})
.unwrap();
store
.submit_review(SubmitReview {
ticket_id: "T1".into(),
revision_id: "V1".into(),
merge_result_id: Some("result-op-merge".into()),
capability_token: "token-merge".into(),
decision: ReviewDecision::Approve,
body: "merge evidence is valid".into(),
findings: vec![],
now: "2026-07-26T00:00:05Z".into(),
})
.unwrap();
let approved = store
.readiness_for_ticket_with_target("T1", Some("T1"))
.unwrap();
assert!(approved.ready, "{:?}", approved.blockers);
assert_eq!(
approved.merge_result_review_status,
Some(ReviewStatus::Approved)
);
let applied = store
.show_for_ticket_with_target("T1", Some("M1"))
.unwrap()
.unwrap();
assert_eq!(
applied
.applied_merge_result
.as_ref()
.map(|result| result.target_status),
Some(MergeResultTargetStatus::Applied)
);
assert!(
store
.readiness_for_ticket_with_target("T1", Some("M1"))
.unwrap()
.ready
);
}
#[test]
fn bounded_context_rejects_oversized_revision_evidence() {
let (_dir, store) = setup();
@@ -142,6 +329,7 @@ fn bounded_context_rejects_oversized_revision_evidence() {
merge_request_id: "MR1".into(),
ticket_id: "T1".into(),
repository_id: "repo".into(),
target_ref_selector: "develop".into(),
revision: oversized,
authenticated_runtime_id: "R1".into(),
authenticated_worker_id: "W1".into(),
@@ -311,7 +499,23 @@ fn v7_completion_operations_are_preserved_as_legacy_assigned_coder_authority() {
|row| row.get(0),
)
.unwrap();
assert_eq!(version, 8);
assert_eq!(version, 9);
let head_tree_columns: i64 = conn
.query_row(
"SELECT COUNT(*) FROM pragma_table_info('merge_request_revisions') WHERE name='head_tree'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(head_tree_columns, 0);
let merge_result_table: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='merge_request_merge_results'",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(merge_result_table, 1);
}
#[test]
@@ -431,6 +635,7 @@ fn spoof_self_approval_replay_and_cross_workspace_are_rejected() {
attempt_id: "bad".into(),
ticket_id: "T1".into(),
revision_id: "V1".into(),
merge_result_id: None,
parent_assignment_id: "A1".into(),
parent_runtime_id: "R1".into(),
parent_worker_id: "W1".into(),