fix(harness): atomic claim for auto-approval and rejected proposals stay rejected

보안 리뷰 반영. 자동 승인은 approved_at·executed_at·rejected_at 이 비어 있고 만료 전인
행만 조건부 UPDATE 로 선점한 경우에만 실행하므로, 사람의 승인·실행·거절이 먼저 오면
이중 실행이 없다. proposals 에 rejected_at 열을 추가해 거절 경로가 기록하고,
approve/authorize/자동 승인이 거절된 제안을 거부한다.
This commit is contained in:
seon committed 2026-10-15 23:41:44 +09:00
1 parent ca933a646e
commit 85d99324a1
5 files changed
+34 -8

No files matched your search

+4
View File
@@ -94,6 +94,9 @@ func (a *App) handleProposalApprove(w http.ResponseWriter, r *http.Request) {
return
}
switch {
case p.RejectedAt != nil:
writeErr(w, 409, errors.New("proposal rejected"))
return
case p.ApprovedAt != nil:
writeErr(w, 409, errors.New("proposal already approved"))
return
@@ -122,6 +125,7 @@ func (a *App) handleProposalReject(w http.ResponseWriter, r *http.Request) {
writeErr(w, 404, errors.New("unknown proposal"))
return
}
_ = a.store.RejectProposal(r.Context(), token) // blocks a later approval (button, API or auto-approval)
a.store.Event(r.Context(), p.RunID, p.ScenarioID, "note", req.RejectedBy, map[string]any{"rejected": true, "action_id": p.ActionID, "reason": req.Reason, "token_suffix": token[len(token)-6:]})
writeJSON(w, 200, map[string]any{"token": token, "rejected": true})
}