fix: integrate serialized orchestrator attention

This commit is contained in:
2026-10-05 18:10:12 +09:00
3 changed files with 1175 additions and 112 deletions
+80
View File
@@ -13290,6 +13290,86 @@ permission = "write"
assert_eq!(state.pending_notifications[1].provenance, account_b);
}
#[test]
fn notification_lost_acceptance_retry_after_restore_commits_once_not_by_body() {
let temp = tempfile::tempdir().unwrap();
let handle = PendingSubmissionHandle::for_test(temp.path());
let source = "backend:orchestrator-attention";
let accept = |handle: &PendingSubmissionHandle<session_store::FsStore>, id: &str| {
handle
.accept_notification_from_source(
id.into(),
"Queued Tickets require attention".into(),
source.into(),
WorkerHistoryProvenance::BackendInstruction { operation_id: None },
)
.unwrap()
};
// The sender lost this acceptance response; the receipt is already durable.
assert!(accept(&handle, "attention-1"));
let restored_state: PendingActivationState = handle
.persisted_entries_for_test()
.iter()
.rev()
.find_map(|entry| match entry {
LogEntry::Extension {
domain, payload, ..
} if domain == SESSION_PENDING_ACTIVATIONS_EXTENSION_DOMAIN => {
Some(serde_json::from_value(payload.clone()).unwrap())
}
_ => None,
})
.unwrap();
assert_eq!(restored_state.notification_receipts.len(), 1);
let handle = PendingSubmissionHandle {
state: Arc::new(Mutex::new(restored_state)),
writer: handle.writer.clone(),
};
assert!(!accept(&handle, "attention-1"));
let commit_batch = |handle: &PendingSubmissionHandle<session_store::FsStore>| {
let batch = handle.prepare_notification_batch();
let count = batch.len();
for (notification, extension) in batch {
handle
.writer
.commit_system_item_with_extensions(
SystemItem::Notification {
body: notification.message.clone(),
message: notification.message,
prompt_provenance: None,
},
vec![extension],
Some(notification.provenance),
)
.unwrap();
}
handle.finish_notification_batch();
count
};
assert_eq!(commit_batch(&handle), 1);
assert!(!accept(&handle, "attention-1"));
assert_eq!(commit_batch(&handle), 0);
let notification_count = |handle: &PendingSubmissionHandle<session_store::FsStore>| {
handle
.persisted_entries_for_test()
.iter()
.filter(|entry| {
matches!(entry,
LogEntry::AnnotatedSystemItem { entry, .. }
if matches!(entry.item, SystemItem::Notification { .. })
)
})
.count()
};
assert_eq!(notification_count(&handle), 1);
assert_eq!(handle.state.lock().unwrap().notification_receipts.len(), 1);
// Same body with a new logical ID is legitimate, not content-deduped.
assert!(accept(&handle, "attention-2"));
assert_eq!(commit_batch(&handle), 1);
assert_eq!(notification_count(&handle), 2);
assert_eq!(handle.state.lock().unwrap().notification_receipts.len(), 2);
}
#[test]
fn notification_batch_preserves_fifo_order_and_notification_dedupes() {
let temp = tempfile::tempdir().unwrap();
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,30 @@
# Reviewer verdict のvalidation rejectionとone-call制約
## 確認した事実
T-703のMR `01a10b3b-87ec-7093-b0c4-a00a78095695`、source
`3174142bd1314fe34c5de7b6a66654ff1c2a8af2` の独立ReviewerはRestoreとno-op
attention checkの競合を見つけ、`ReviewMergeRequest`でrequest_changesを提出しようとした。
severityに`medium`を指定したためHTTP 422となった。Backendの許容値は
`blocker` / `major` / `minor` / `note` だった。
親の`ShowMergeRequest`再読でthreadはReviewRequested seq2までであり、verdictが
永続化されていないことを確認した。Reviewerは「ReviewMergeRequestを正確に一度だけ
呼ぶ」という指示に従い、validation rejection後も再提出を行わなかった。親のfollow-upでも
同じ境界を報告したため、再提出を強制せずReviewerを停止した。
## 障壁
正しいBackend拒否を迂回すべきではない一方、mutationが行われていない入力validation失敗が
一回限りの提出枠を消費する扱いだと、レビュー済みの指摘がMR authorityに残らずpendingになる。
セッションでの報告をverdictや承認の代わりにはできない。
## 改善案(このTicketの実装対象ではない)
- Reviewer-facing tool schemaにseverityのBackend enumを明示し、不正値を入力時に防ぐ。
- one-call指示について「一度のdurable verdict」と「validationで拒否された呼出」を区別できるか
設計上判断する。無制限再提出や記録済みverdictの変更を認める提案ではない。
- mutationなし/retry可否をtyped errorとして返せると、親とReviewerの双方が推測なしに扱える。
T-703では元のMR/source selectorを保持し、指摘を修正したcommitを通常pushした後、fresh
exact-source reviewで修正内容とその証拠をMRに記録する。未記録のverdictを承認扱いしない。